Compare commits
855 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
bc27144430 | ||
|
f0aab63319 | ||
|
d099fafd65 | ||
|
cf32578f25 | ||
|
e76e60d3c0 | ||
|
674fc1583f | ||
|
da86ebae9c | ||
|
ad8fe1e89a | ||
|
1ddd281893 | ||
|
976369857e | ||
|
64e8a2bdb1 | ||
|
bbaa093dbc | ||
|
fa9424b05a | ||
|
4018769a30 | ||
|
b5a02d7300 | ||
|
b63eb9121f | ||
|
77204cc7e8 | ||
|
919dc8fdb3 | ||
|
064a54be6c | ||
|
cd1c44a4aa | ||
|
1551c32371 | ||
|
a9cf00425e | ||
|
529b5b043e | ||
|
b7b2bc19e9 | ||
|
f9f4ce5bc1 | ||
|
93ef9e0ea9 | ||
|
fff02307ac | ||
|
59c5003ceb | ||
|
98d5adf924 | ||
|
9e5c8a113f | ||
|
9fcff671ff | ||
|
3170e7df6f | ||
|
f208ffcc9a | ||
|
9056eccea6 | ||
|
99228e4743 | ||
|
9cdc025759 | ||
|
3835b48d80 | ||
|
3efc0a8982 | ||
|
f27f7ab801 | ||
|
051a635f4b | ||
|
4860f75372 | ||
|
073faf7539 | ||
|
18524c6e89 | ||
|
d51c392c2c | ||
|
d83a2818e7 | ||
|
1958cb1ce2 | ||
|
d7d5a6a36f | ||
|
5996054fd4 | ||
|
d8bfca25fa | ||
|
4abafa5c66 | ||
|
89107f9889 | ||
|
b5245081d9 | ||
|
04515f38b3 | ||
|
6933e9b70f | ||
|
6d82d716c2 | ||
|
7dc694150d | ||
|
c9cc1b30ec | ||
|
c0540b7ba3 | ||
|
87e655f306 | ||
|
cd2cdfd446 | ||
|
f56e9387c8 | ||
|
18154c8332 | ||
|
4b34b509fe | ||
|
d820a58314 | ||
|
cc5d607766 | ||
|
ceb01e42e8 | ||
|
074506e67c | ||
|
898f203584 | ||
|
f0f549d7ef | ||
|
caa4ff42f8 | ||
|
13732f7ff2 | ||
|
8cbfd758c2 | ||
|
9487833b42 | ||
|
7e12133b92 | ||
|
1206b3917c | ||
|
d0e0436e24 | ||
|
25da081f59 | ||
|
1e81e2c71a | ||
|
b8e2e454df | ||
|
09a635f30e | ||
|
fa6075c0f9 | ||
|
53c7b499db | ||
|
639e1b52e7 | ||
|
342e7e65cd | ||
|
3e27447a80 | ||
|
63ba2aaf0c | ||
|
5fccc20854 | ||
|
28a097f756 | ||
|
5ccddaafef | ||
|
ea30bd0b54 | ||
|
230fa1b1f5 | ||
|
7ce686503a | ||
|
26168c4ce4 | ||
|
63c38f7716 | ||
|
e88b55a3d4 | ||
|
1eca6a987d | ||
|
0580f101ce | ||
|
30c4205958 | ||
|
c88fa65e07 | ||
|
228f660a92 | ||
|
17d586cb2f | ||
|
15788115f5 | ||
|
1518a984c0 | ||
|
d1743e6476 | ||
|
3af5211900 | ||
|
1e3e4e056d | ||
|
83676df431 | ||
|
baa7b2724a | ||
|
b47e22bf5d | ||
|
2824eaf968 | ||
|
990799a850 | ||
|
be98e8ef6f | ||
|
b54e7aefcc | ||
|
de709cda8d | ||
|
e0ce4a4d79 | ||
|
86298d0c45 | ||
|
d40f3dbb99 | ||
|
819cfe7f24 | ||
|
98947e6853 | ||
|
6d37ef7e3c | ||
|
111d354b7f | ||
|
75dfbd6275 | ||
|
450b0d9ff8 | ||
|
39f1b35db6 | ||
|
f6c38689c6 | ||
|
2b9a6241c6 | ||
|
cc7e7bcea9 | ||
|
f26715c9f3 | ||
|
bf3ed115fa | ||
|
f77d3a18ab | ||
|
a384efbc72 | ||
|
85577e2613 | ||
|
0e16de8e5a | ||
|
16934978e8 | ||
|
8d7e5a7e0f | ||
|
a34861a3b0 | ||
|
20a5d573f9 | ||
|
86aa000b2d | ||
|
c02a104469 | ||
|
9a16831147 | ||
|
1914d594c4 | ||
|
3ee9c81335 | ||
|
c6668ef8a8 | ||
|
4ad9c1e921 | ||
|
8af0c875bc | ||
|
2862ae6212 | ||
|
12bab73ec3 | ||
|
93affec586 | ||
|
5d28006566 | ||
|
ebdcee2b6e | ||
|
26a35fd76a | ||
|
36916f0759 | ||
|
4024f4d402 | ||
|
fe6e71bbe0 | ||
|
e1dbdf065e | ||
|
7926e3aa89 | ||
|
4816043101 | ||
|
b3b922949d | ||
|
89b1fe3da9 | ||
|
43d6797770 | ||
|
5e01af3828 | ||
|
646c30b8cb | ||
|
591b58fc06 | ||
|
1ddf062331 | ||
|
1a5b794b88 | ||
|
8a6a12100d | ||
|
6799f5852d | ||
|
5d4c1fb962 | ||
|
af116aa0a3 | ||
|
7f444c3971 | ||
|
69e88b4d52 | ||
|
cfbea91a69 | ||
|
fecc0a24cb | ||
|
607a725719 | ||
|
9d4486616a | ||
|
d420a12a57 | ||
|
fcb2003178 | ||
|
71a70f1d8c | ||
|
6ea010be93 | ||
|
257268e0c8 | ||
|
a2b5f7f3a8 | ||
|
56d58442e5 | ||
|
24eafaa99a | ||
|
24b3fd36e6 | ||
|
8f00640dd5 | ||
|
fc11225474 | ||
|
f003443acc | ||
|
b2390ec2b3 | ||
|
3fc35dac23 | ||
|
948274e25e | ||
|
46f3b678f0 | ||
|
85dfdf824c | ||
|
33563e3d63 | ||
|
26d47ca5bb | ||
|
816d824bc1 | ||
|
9f80f875f7 | ||
|
ae55a744d1 | ||
|
7f004913eb | ||
|
577f0d3e75 | ||
|
996028a165 | ||
|
026e9569a1 | ||
|
295f112356 | ||
|
9825916400 | ||
|
309797d573 | ||
|
5f88f8ff85 | ||
|
565f463fdc | ||
|
50e3ebb805 | ||
|
0a5714e8c8 | ||
|
88d16b7e2d | ||
|
2d60ce0ac7 | ||
|
cb47eca810 | ||
|
93a693b9ef | ||
|
c9b8490bb7 | ||
|
1c3e27616d | ||
|
b1165eac65 | ||
|
a555580f5f | ||
|
5b8c8a6f2e | ||
|
23e2495636 | ||
|
9ef407a028 | ||
|
65b8d3904f | ||
|
64058b983c | ||
|
cc749c612d | ||
|
746ab737b5 | ||
|
3caf118cbc | ||
|
d159fac7b5 | ||
|
c6bff34abb | ||
|
498f6027a1 | ||
|
de1e9f82d2 | ||
|
b3f417dc20 | ||
|
de78dff26d | ||
|
c010031269 | ||
|
ceeaa7476f | ||
|
1d567df2ee | ||
|
77a464344a | ||
|
5366029282 | ||
|
506e97afe6 | ||
|
04c43898ee | ||
|
fdf797bb28 | ||
|
0f9626c48d | ||
|
dd1552f3ff | ||
|
94b7bf4bf8 | ||
|
7de0a389dc | ||
|
1dd6d98a28 | ||
|
abe78b5df5 | ||
|
fa26357178 | ||
|
120c56988e | ||
|
f1f36be663 | ||
|
f0bc54fecb | ||
|
b79a36f65f | ||
|
4f6d2f830e | ||
|
babfdeaf04 | ||
|
c95038abb5 | ||
|
9fa6a7e2aa | ||
|
c0d97cefa5 | ||
|
33235f482e | ||
|
b183095c52 | ||
|
01e840e151 | ||
|
cc5391bc0d | ||
|
e640be9866 | ||
|
59c32867b3 | ||
|
0b719c18a6 | ||
|
d143b2d4f0 | ||
|
006940a359 | ||
|
fe600c72ad | ||
|
7d727f4f02 | ||
|
fa612a060a | ||
|
ce0fdf8bca | ||
|
da878eaf38 | ||
|
456191715a | ||
|
4b6162d364 | ||
|
83a97b72df | ||
|
08b7d4d3b7 | ||
|
ca37a9420e | ||
|
a1ee375bcd | ||
|
6333b73d13 | ||
|
a089fb83fa | ||
|
fd1007ae33 | ||
|
66d5dae371 | ||
|
6ed220cabf | ||
|
8727853801 | ||
|
0e8bfa592f | ||
|
bc4eb877c9 | ||
|
274f5fa6ba | ||
|
8ca6a7eb58 | ||
|
4dd36897af | ||
|
830b696ae7 | ||
|
63c33d234d | ||
|
1f496a7f17 | ||
|
11d793dd7e | ||
|
53e4baedc7 | ||
|
67c5f7b239 | ||
|
a582495311 | ||
|
048718fb79 | ||
|
e82694793b | ||
|
41d87d6225 | ||
|
bf7d855598 | ||
|
38d9398559 | ||
|
0787548639 | ||
|
952dac0b4c | ||
|
a0c4a1cd03 | ||
|
21ac595919 | ||
|
fbb441eca2 | ||
|
33ee1c4eed | ||
|
20be816bc8 | ||
|
8d379a16fe | ||
|
99afc0ff6f | ||
|
506954e5fe | ||
|
14d0dd70c4 | ||
|
850155eb41 | ||
|
118f896ac3 | ||
|
e7ab94f169 | ||
|
0ed6480f6e | ||
|
d22dd00856 | ||
|
086bb62217 | ||
|
18b9bd5e0c | ||
|
f68a821dde | ||
|
023cc41335 | ||
|
453b26f1f0 | ||
|
687108bb4d | ||
|
d67d012dd5 | ||
|
968dfc18ba | ||
|
e41668ee59 | ||
|
2457525702 | ||
|
97a3bc2386 | ||
|
e111db34c3 | ||
|
47411d8154 | ||
|
ebbbab5c2e | ||
|
b766d1f848 | ||
|
1595e6fc05 | ||
|
8c746f68e3 | ||
|
01c25e880c | ||
|
95667f848a | ||
|
4e9f4b06f1 | ||
|
0977306827 | ||
|
d56742a174 | ||
|
7326cd8bfd | ||
|
680407daa9 | ||
|
571dc11c2f | ||
|
0461c4b321 | ||
|
85b46bef6c | ||
|
a5b7a6bc81 | ||
|
8ee406d788 | ||
|
868fe9bb18 | ||
|
9af091f9c4 | ||
|
6521a1606e | ||
|
5acab36239 | ||
|
e02a8c1009 | ||
|
f6130995eb | ||
|
90b4564cec | ||
|
722821fab7 | ||
|
019e1a8417 | ||
|
41c0dc7edf | ||
|
87ecaf9682 | ||
|
680b9f8a6c | ||
|
c26df87b86 | ||
|
3b3de79e3d | ||
|
3ca146de4d | ||
|
e33c559345 | ||
|
f0c76f826f | ||
|
5547e9131a | ||
|
864a7e52f3 | ||
|
b4ad6e386e | ||
|
f6401225a4 | ||
|
5d24afc373 | ||
|
e2c56e90c9 | ||
|
52e30bf8e8 | ||
|
284d920cf3 | ||
|
e80b829976 | ||
|
1042a45618 | ||
|
a4837c8509 | ||
|
d77c9efee9 | ||
|
8560572d16 | ||
|
614c26fe9d | ||
|
099dd45f63 | ||
|
47dcf037a7 | ||
|
008321b24f | ||
|
e346b8bdb4 | ||
|
b30ec73e99 | ||
|
6015ae97d6 | ||
|
024bfebd1a | ||
|
8a43a47919 | ||
|
ff9dc1d121 | ||
|
cd9f83d86c | ||
|
9f8e13753b | ||
|
94e4e48ef4 | ||
|
bf454e6369 | ||
|
688d5706b4 | ||
|
41bc84e15f | ||
|
dac0eee62d | ||
|
df5fc690df | ||
|
5b6f9d87d3 | ||
|
61d466df87 | ||
|
a0fd827dc5 | ||
|
d2372224f2 | ||
|
aea390a67c | ||
|
cbedb826c9 | ||
|
b4b328b720 | ||
|
5a2bb2e5e6 | ||
|
b322331923 | ||
|
7ba417400f | ||
|
6833062a5c | ||
|
5efd43f749 | ||
|
24b99b8231 | ||
|
e4aee9f7f2 | ||
|
3301356e7d | ||
|
9726bc2029 | ||
|
15595d0370 | ||
|
a53a4360e2 | ||
|
465b1db267 | ||
|
7856e18a31 | ||
|
073bc25cd4 | ||
|
970a1922c0 | ||
|
f1768df507 | ||
|
475ddeab13 | ||
|
0ebc233513 | ||
|
69f3078825 | ||
|
4d49b031cd | ||
|
0b9552e707 | ||
|
9379c716d4 | ||
|
e7ddc2d298 | ||
|
108a0a7b74 | ||
|
5a9644937e | ||
|
0c873c1500 | ||
|
f33e2fa1f8 | ||
|
d5eaa4ffe0 | ||
|
b5f9936dbe | ||
|
e4b4b97307 | ||
|
27a6509797 | ||
|
c638dbcb4b | ||
|
4e358a6741 | ||
|
2c4087d78f | ||
|
520e3578d4 | ||
|
c82135056b | ||
|
b1b617e53f | ||
|
0294fed33c | ||
|
43cd327f19 | ||
|
9e4fa74515 | ||
|
68c53c448f | ||
|
48e6c3f820 | ||
|
e331955fd8 | ||
|
b5bbf1f2d2 | ||
|
9de70e0184 | ||
|
9c3b8f40cb | ||
|
993a483ec3 | ||
|
c844e4c53e | ||
|
e78a1e022a | ||
|
708ca5b9ab | ||
|
f7f91b7077 | ||
|
232ec3892e | ||
|
a91581a04e | ||
|
296f336b62 | ||
|
07ddcb93f5 | ||
|
72b917c8be | ||
|
83d933a61e | ||
|
75b9804e33 | ||
|
18c204e3a5 | ||
|
edcbd9f8e4 | ||
|
b85589c72a | ||
|
0aea513d72 | ||
|
6394bc08a5 | ||
|
711c257295 | ||
|
55c0b742ec | ||
|
cccc145e2f | ||
|
940e01ce38 | ||
|
6c7bf772b9 | ||
|
dcae077607 | ||
|
9882080737 | ||
|
435e45b154 | ||
|
872a249fa7 | ||
|
f8615d5255 | ||
|
ba7b82d9a2 | ||
|
80ba52c721 | ||
|
bfe360a0a7 | ||
|
bfdb66ab71 | ||
|
86d3cf2e8b | ||
|
36ff4a5c3e | ||
|
b35f6a2480 | ||
|
8dae875906 | ||
|
a5a398c231 | ||
|
6c117f47b9 | ||
|
24cf49c216 | ||
|
abade0d453 | ||
|
dbe4fae178 | ||
|
956c53f99a | ||
|
3575ad6485 | ||
|
b863c167d6 | ||
|
c0c911d0ee | ||
|
c3bbb16dd3 | ||
|
df8b15e720 | ||
|
6fdc48e163 | ||
|
249835f8b6 | ||
|
1418454f04 | ||
|
7dded89b33 | ||
|
f4ae96ea9b | ||
|
e8792e43d5 | ||
|
f825112db3 | ||
|
e1c71d4d57 | ||
|
ffbc597146 | ||
|
d0ed8f891f | ||
|
8e31ce1684 | ||
|
90122910d1 | ||
|
25c4791791 | ||
|
8d2eb3c079 | ||
|
fff3227cee | ||
|
52b260112a | ||
|
25979431e4 | ||
|
4a2d1e6b3d | ||
|
d8ccce2bc0 | ||
|
8b71ba3320 | ||
|
bbb015b500 | ||
|
657b0231f8 | ||
|
869cf338de | ||
|
ac27ce502e | ||
|
8fa2b46196 | ||
|
8585604508 | ||
|
432fcd267d | ||
|
0ec66e8959 | ||
|
70764fb34e | ||
|
7eae1cbf23 | ||
|
2a2a0ebfe5 | ||
|
a285d7a8b6 | ||
|
26c542d9e1 | ||
|
6cd8c5dc35 | ||
|
0826817f4e | ||
|
5d3e667c1a | ||
|
9070f6bf2e | ||
|
275d1e3daa | ||
|
62cbdb9d78 | ||
|
08cd1b3be9 | ||
|
caf7545906 | ||
|
75605b9c36 | ||
|
2f96d9b3a7 | ||
|
cf078cf20b | ||
|
94a78e7eba | ||
|
637c81196b | ||
|
5cf6753e60 | ||
|
a50fcc413b | ||
|
32be8ce245 | ||
|
a8295c2c4a | ||
|
5ca8630d9b | ||
|
112331a325 | ||
|
32eacfd4ee | ||
|
a963c12a82 | ||
|
f851834ccd | ||
|
8434b225a1 | ||
|
f1d72a4cdf | ||
|
79da580283 | ||
|
681b6baa33 | ||
|
31d03bec46 | ||
|
78fd49eecf | ||
|
069b422750 | ||
|
9f99e11596 | ||
|
1c3bf50d8a | ||
|
caa877dc83 | ||
|
4400acfd2b | ||
|
a2becaaf6a | ||
|
1a2970eba0 | ||
|
86fd91cf04 | ||
|
eb9660a818 | ||
|
eb80f9aa26 | ||
|
d5906c100a | ||
|
ad9a3965ee | ||
|
d76489a062 | ||
|
06e364d998 | ||
|
75f3ca2443 | ||
|
f97523dbf1 | ||
|
c17f918425 | ||
|
3acd1c8d31 | ||
|
f0b780f822 | ||
|
1818542ab7 | ||
|
bd8d044507 | ||
|
1a9312ce82 | ||
|
d4f062a56b | ||
|
9214d0b8ee | ||
|
11f27d2214 | ||
|
06b4fac02b | ||
|
91ba32e9d2 | ||
|
43233f5766 | ||
|
7af105dabf | ||
|
66384d32dd | ||
|
8e89cab029 | ||
|
7ad1bcd2ed | ||
|
272de2e480 | ||
|
a217471dc6 | ||
|
3abe9d1eea | ||
|
9265247834 | ||
|
be097190e9 | ||
|
4b63db64a0 | ||
|
8a65a58de8 | ||
|
b4a27f3734 | ||
|
980a9514e0 | ||
|
84200628ee | ||
|
07788ab977 | ||
|
d9ec3a5f48 | ||
|
03a0217354 | ||
|
71c78694d4 | ||
|
c8cf1ac535 | ||
|
b9a7560f72 | ||
|
7c9a322b0d | ||
|
7280a3803d | ||
|
d91a5f4567 | ||
|
def850b619 | ||
|
e74dbad644 | ||
|
f6ee27d0c7 | ||
|
aa125e1bc1 | ||
|
39058484d4 | ||
|
522b0d595d | ||
|
913583ff11 | ||
|
5d62e6f70f | ||
|
e4f41053a0 | ||
|
17b576ef12 | ||
|
bd684191dd | ||
|
a8cd68e18f | ||
|
474b2b690f | ||
|
e54c86a6ce | ||
|
8230d11214 | ||
|
1fa623b35a | ||
|
cda3a0171d | ||
|
34bf23ae40 | ||
|
7aa80e21b9 | ||
|
342c2e4eb8 | ||
|
ef8d9eb2bc | ||
|
4656ca4283 | ||
|
d986824c32 | ||
|
e2c07759de | ||
|
5063ffe963 | ||
|
ffc6911ed3 | ||
|
e500ccd42d | ||
|
d16ea15626 | ||
|
9e29d03f63 | ||
|
6394090d1b | ||
|
6167052fc7 | ||
|
a2161e10a8 | ||
|
5b924852fb | ||
|
6f0168b20b | ||
|
f50de1c6ed | ||
|
88698363fd | ||
|
224dd98ebe | ||
|
14b596fd30 | ||
|
6ff61c0ecc | ||
|
c8c9a0217c | ||
|
0a47c7b115 | ||
|
a1f698ef44 | ||
|
c5632466d9 | ||
|
752b67cad4 | ||
|
e955e55cb7 | ||
|
94a26eb1a8 | ||
|
bd322bba6b | ||
|
4102f3add4 | ||
|
626d68f6ff | ||
|
82c82a9a8f | ||
|
c58723d028 | ||
|
490ee83ddf | ||
|
4d88475295 | ||
|
609d1768cb | ||
|
385203be78 | ||
|
780222e1ec | ||
|
e84d1653b7 | ||
|
5b97393d2b | ||
|
96047c855b | ||
|
0d3e566f68 | ||
|
ea1142a4a2 | ||
|
338de4d938 | ||
|
148b99ee0f | ||
|
726de4a186 | ||
|
2730f1247a | ||
|
f6e8d7ad58 | ||
|
907c5190b1 | ||
|
73747c66b7 | ||
|
c801391d18 | ||
|
1d3d675821 | ||
|
04dff67cfa | ||
|
28e995d9c6 | ||
|
b9ea1d92ce | ||
|
00cbb94f3d | ||
|
5f276b36fa | ||
|
dcb7030289 | ||
|
e8382421aa | ||
|
04b90eb43e | ||
|
ab33c8ace1 | ||
|
ded4f6ef9a | ||
|
e342992751 | ||
|
dbba0734d6 | ||
|
c5ceb2679c | ||
|
6b0eb71322 | ||
|
ef4044281d | ||
|
8ba22d5da2 | ||
|
2a04aeadb3 | ||
|
f3e9adba3b | ||
|
5b49e6c9b2 | ||
|
c209c41da6 | ||
|
fb21e2db1a | ||
|
1a7ce3e367 | ||
|
8140cba74c | ||
|
af1c1b5bb6 | ||
|
f8f5985f3e | ||
|
376eb2307a | ||
|
49073081c8 | ||
|
cfb80e55af | ||
|
5d4ed5e401 | ||
|
b80111bed5 | ||
|
1fe9763c19 | ||
|
1c0f43b9fd | ||
|
801f7d517e | ||
|
d657e3d3c3 | ||
|
ea61fd7e44 | ||
|
06a7c74162 | ||
|
f1d0b3933d | ||
|
7931f07d6f | ||
|
228df3da71 | ||
|
15303a3415 | ||
|
a41b6ac8d9 | ||
|
096a1c8bac | ||
|
61baf2b466 | ||
|
2c9a288046 | ||
|
c8bee3ed22 | ||
|
4d97cc13ac | ||
|
5f55e62e88 | ||
|
4171215da7 | ||
|
f46a17573d | ||
|
c1febd329f | ||
|
c9fcbcc048 | ||
|
4c7e0a7717 | ||
|
5bbf4eb3ef | ||
|
c2719dc5cb | ||
|
9e23ce0be5 | ||
|
3396561021 | ||
|
5d469d7ece | ||
|
0af6142f47 | ||
|
2aa4d808a7 | ||
|
2102b439c4 | ||
|
398f2cb236 | ||
|
e35c49efa8 | ||
|
127555abcf | ||
|
fd7dbc709b | ||
|
03ff75a189 | ||
|
6a62558687 | ||
|
8c66270eb5 | ||
|
190467254e | ||
|
cc703af68d | ||
|
08c107516d | ||
|
558746a776 | ||
|
d3fc6ec365 | ||
|
5adaaab45c | ||
|
94f4175059 | ||
|
afd5e634cc | ||
|
f3cf995140 | ||
|
f9ffeb26f2 | ||
|
ff2a6d7eda | ||
|
768c5151d2 | ||
|
5e462f6ce6 | ||
|
bbf937dd44 | ||
|
64d9002b75 | ||
|
420b5a433f | ||
|
7bfac0cf29 | ||
|
9005f4fbff | ||
|
2fa4043f21 | ||
|
9e25ee6adc | ||
|
769efe6af6 | ||
|
5525c49529 | ||
|
25b1055dc0 | ||
|
553b20ac1a | ||
|
9c25d73db7 | ||
|
615ad218b4 | ||
|
e511d2fb29 | ||
|
b000eff257 | ||
|
2cfe8bd4b2 | ||
|
28504a14c2 | ||
|
5ea9b5d709 | ||
|
cf60074f52 | ||
|
7ddccdbd1e | ||
|
ba6247eb35 | ||
|
52a513d232 | ||
|
a68ebbcb72 | ||
|
8cb5865a78 | ||
|
7e6b5c6be2 | ||
|
52833e2f2b | ||
|
7c823b042c | ||
|
62cfe5bed1 | ||
|
3adf411dcd | ||
|
6634449ca0 | ||
|
aa4e33ff50 | ||
|
d579431fa0 | ||
|
cbbce86940 | ||
|
7af02185d5 | ||
|
9cd764b2e7 | ||
|
281e3f4add | ||
|
8abc9e6942 | ||
|
00765145a3 | ||
|
a4ee8a7704 | ||
|
d9c696ff2c | ||
|
dc4d8f620e | ||
|
0abe56597e | ||
|
7f593f6d5f | ||
|
39dd9549f8 | ||
|
627ed8775e | ||
|
f55382880e | ||
|
fc53cc5d2d | ||
|
ff92c9ea04 | ||
|
3a11286c87 | ||
|
e00e900486 | ||
|
de0e96054d | ||
|
b427091f0e | ||
|
fcc4c4043e | ||
|
91ddced8b2 | ||
|
ec709b3974 | ||
|
671e9de604 | ||
|
5cad588227 | ||
|
b2857e6e20 | ||
|
f98c3e92fd | ||
|
b585462dd5 | ||
|
1054fcdf92 | ||
|
03d05a25a9 | ||
|
c8248ba2a4 | ||
|
d3cbcd8825 | ||
|
848251420a | ||
|
9939ba6234 | ||
|
6e3c40f159 | ||
|
6e4a2aaa05 | ||
|
3fbf2d5261 | ||
|
0474e03d2d | ||
|
5ace0ea5ab | ||
|
1f8aae53ae | ||
|
e14fb01063 | ||
|
793e6ba9c9 | ||
|
0e07aaee76 | ||
|
dad29566dc | ||
|
f1b2657523 | ||
|
554780253f | ||
|
a37f8c5c37 | ||
|
ec3ccb128c | ||
|
3ff3dbc43e | ||
|
e77ac3d9e8 | ||
|
afe53e9020 | ||
|
7974c7a95c | ||
|
3b2b86a8e1 | ||
|
8309a875bb | ||
|
82a8a0bd13 | ||
|
c63efc6472 | ||
|
5ee253a9a3 | ||
|
89b3365896 | ||
|
d9661c4567 | ||
|
5f9314fba6 | ||
|
452f04d342 | ||
|
1793f9fd47 | ||
|
33f771d0c4 | ||
|
5cdb17cf10 | ||
|
f2e0e22442 | ||
|
ea9309d7d3 | ||
|
8503baea4b | ||
|
74941bdd46 | ||
|
5f8b09e843 | ||
|
69932412cb | ||
|
4ef728230c | ||
|
ff94676369 |
@ -1,6 +0,0 @@
|
||||
LICENSE.md
|
||||
README.md
|
||||
bin/*
|
||||
lib/**/*.rb
|
||||
test/**/*.rb
|
||||
.github/*.md
|
9
.editorconfig
Normal file
@ -0,0 +1,9 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
89
.github/CONTRIBUTING.md
vendored
@ -1,52 +1,79 @@
|
||||
## Contributing
|
||||
|
||||
You can run the test suite against a live server by running `script/test`. It
|
||||
automatically starts a test server in background. Only tests in
|
||||
`test/adapters/*_test.rb` require a server, though.
|
||||
In Faraday we always welcome new ideas and features, however we also have to ensure
|
||||
that the overall code quality stays on reasonable levels.
|
||||
For this reason, before adding any contribution to Faraday, we highly recommend reading this
|
||||
quick guide to ensure your PR can be reviewed and approved as quickly as possible.
|
||||
|
||||
``` sh
|
||||
# setup development dependencies
|
||||
$ script/bootstrap
|
||||
We are past our 1.0 release, and follow [Semantic Versioning][semver]. If your
|
||||
patch includes changes that break compatibility, note that in the Pull Request, so we can add it to
|
||||
the [Changelog][].
|
||||
|
||||
# run the whole suite
|
||||
$ script/test
|
||||
|
||||
# run only specific files
|
||||
$ script/test excon patron
|
||||
### Policy on inclusive language
|
||||
|
||||
# run tests using SSL
|
||||
$ SSL=yes script/test
|
||||
You have read our [Code of Conduct][], which includes a note about **inclusive language**. This section tries to make that actionable.
|
||||
|
||||
Faraday has a large and diverse userbase. To make Faraday a pleasant and effective experience for everyone, we use inclusive language.
|
||||
|
||||
These resources can help:
|
||||
|
||||
- Google's tutorial [Writing inclusive documentation](https://developers.google.com/style/inclusive-documentation) teaches by example, how to reword non-inclusive things.
|
||||
- Linux kernel mailing list's [Coding Style: Inclusive Terminology](https://lkml.org/lkml/2020/7/4/229) said "Add no new instances of non-inclusive words, here is a list of words not include new ones of."
|
||||
- Linguistic Society of America published [Guidelines for Inclusive Language](https://www.linguisticsociety.org/resource/guidelines-inclusive-language) which concluded: "We encourage all linguists to consider the possible reactions of their potential audience to their writing and, in so doing, to choose expository practices and content that is positive, inclusive, and respectful."
|
||||
|
||||
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://ruby.social/@iMacTia) and [@olleolleolle](https://ruby.social/@olleolleolle) on the Mastodon instance ruby.social.
|
||||
|
||||
|
||||
### Required Checks
|
||||
|
||||
Before pushing your code and opening a PR, we recommend you run the following checks to avoid
|
||||
our GitHub Actions Workflow to block your contribution.
|
||||
|
||||
```bash
|
||||
# Run unit tests and check code coverage
|
||||
$ bundle exec rspec
|
||||
|
||||
# Check code style
|
||||
$ bundle exec rubocop
|
||||
```
|
||||
|
||||
|
||||
### New Features
|
||||
|
||||
When adding a feature Faraday:
|
||||
When adding a feature in Faraday:
|
||||
|
||||
1. also add tests to cover your new feature.
|
||||
2. if the feature is for an adapter, the **attempt** must be made to add the same feature to all other adapters as well.
|
||||
3. start opening an issue describing how the new feature will work, and only after receiving the green light by the core team start working on the PR.
|
||||
|
||||
### New Middlewares
|
||||
|
||||
We will accept middleware that:
|
||||
|
||||
1. is useful to a broader audience, but can be implemented relatively
|
||||
simple; and
|
||||
2. which isn't already present in [faraday_middleware][] project.
|
||||
3. start opening an issue describing how the new feature will work, and only after receiving
|
||||
the green light by the core team start working on the PR.
|
||||
|
||||
|
||||
### New Adapters
|
||||
### New Middleware & Adapters
|
||||
|
||||
We will accept adapters that:
|
||||
We prefer new adapters and middlewares to be added **as separate gems**. We can link to such gems from this project.
|
||||
|
||||
This goes for the [faraday_middleware][] project as well.
|
||||
|
||||
We encourage adapters that:
|
||||
|
||||
1. support SSL & streaming;
|
||||
1. are proven and may have better performance than existing ones; or
|
||||
2. if they have features not present in included adapters.
|
||||
1. have features not present in included adapters.
|
||||
|
||||
We are pushing towards a 1.0 release, when we will have to follow [Semantic
|
||||
Versioning][semver]. If your patch includes changes to break compatibility,
|
||||
note that so we can add it to the [Changelog][].
|
||||
|
||||
[semver]: http://semver.org/
|
||||
[changelog]: https://github.com/lostisland/faraday/releases
|
||||
[faraday_middleware]: https://github.com/lostisland/faraday_middleware/wiki
|
||||
### Changes to the Faraday Docs
|
||||
|
||||
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.
|
||||
|
||||
|
||||
[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
@ -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"
|
69
.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,69 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
push:
|
||||
branches: [ main, 1.x, 0.1x ]
|
||||
|
||||
env:
|
||||
GIT_COMMIT_SHA: ${{ github.sha }}
|
||||
GIT_BRANCH: ${{ github.ref }}
|
||||
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
jobs:
|
||||
linting:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
BUNDLE_WITH: lint
|
||||
BUNDLE_WITHOUT: development:test
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Setup Ruby 3.x
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 3
|
||||
bundler-cache: true
|
||||
|
||||
- name: Rubocop
|
||||
run: bundle exec rubocop --format progress
|
||||
|
||||
- name: Yard-Junk
|
||||
run: bundle exec yard-junk --path lib
|
||||
|
||||
build:
|
||||
needs: [ linting ]
|
||||
runs-on: ubuntu-latest
|
||||
name: build ${{ matrix.ruby }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
ruby: [ '3.0', '3.1', '3.2', '3.3', '3.4' ]
|
||||
experimental: [false]
|
||||
include:
|
||||
- ruby: head
|
||||
experimental: true
|
||||
- ruby: truffleruby-head
|
||||
experimental: true
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: ${{ matrix.ruby }}
|
||||
bundler-cache: true
|
||||
|
||||
- name: RSpec
|
||||
continue-on-error: ${{ matrix.experimental }}
|
||||
run: bundle exec rake
|
||||
|
||||
- name: Test External Adapters
|
||||
continue-on-error: ${{ matrix.experimental }}
|
||||
run: bundle exec bake test:external
|
||||
|
||||
|
26
.github/workflows/publish.yml
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
name: Publish
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Publish to Rubygems
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Setup Ruby 3.x
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
bundler-cache: true
|
||||
ruby-version: 3
|
||||
|
||||
- name: Publish to RubyGems
|
||||
uses: rubygems/release-gem@v1
|
15
.github/workflows/refresh_team_page.yml
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
name: Refresh Team Page
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
|
||||
permissions: {}
|
||||
jobs:
|
||||
build:
|
||||
name: Refresh Contributors Stats
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Call GitHub API
|
||||
run: |
|
||||
curl "https://api.github.com/repos/${{ github.repository }}/stats/contributors"
|
8
.gitignore
vendored
@ -7,16 +7,22 @@ pkg/*
|
||||
tmp
|
||||
.rvmrc
|
||||
.ruby-version
|
||||
.yardoc
|
||||
.DS_Store
|
||||
|
||||
## BUNDLER
|
||||
bin
|
||||
*.gem
|
||||
.bundle
|
||||
Gemfile.lock
|
||||
vendor/bundle
|
||||
external
|
||||
|
||||
## NPM
|
||||
node_modules
|
||||
|
||||
## PROJECT::SPECIFIC
|
||||
.rbx
|
||||
|
||||
## IDEs
|
||||
.idea/
|
||||
.yardoc/
|
||||
|
200
.rubocop.yml
Normal file
@ -0,0 +1,200 @@
|
||||
inherit_from: .rubocop_todo.yml
|
||||
|
||||
require:
|
||||
- rubocop-packaging
|
||||
- rubocop-performance
|
||||
|
||||
AllCops:
|
||||
DisplayCopNames: true
|
||||
DisplayStyleGuide: true
|
||||
TargetRubyVersion: 3.0
|
||||
|
||||
# Custom config
|
||||
Gemspec/RequireMFA: # we don't know if this works with auto-deployments yet
|
||||
Enabled: false
|
||||
Layout/LineLength:
|
||||
Exclude:
|
||||
- spec/**/*.rb
|
||||
- examples/**/*.rb
|
||||
Metrics/BlockLength:
|
||||
Exclude:
|
||||
- lib/faraday/options/env.rb
|
||||
- spec/**/*.rb
|
||||
- examples/**/*.rb
|
||||
Metrics/ModuleLength:
|
||||
Exclude:
|
||||
- lib/faraday/options/env.rb
|
||||
Style/Documentation:
|
||||
Exclude:
|
||||
- 'spec/**/*'
|
||||
- 'examples/**/*'
|
||||
Style/DoubleNegation:
|
||||
Enabled: false
|
||||
Style/IfUnlessModifier:
|
||||
Enabled: false
|
||||
|
||||
# New cops
|
||||
Gemspec/DeprecatedAttributeAssignment: # new in 1.30
|
||||
Enabled: true
|
||||
Layout/LineContinuationLeadingSpace: # new in 1.31
|
||||
Enabled: true
|
||||
Layout/LineContinuationSpacing: # new in 1.31
|
||||
Enabled: true
|
||||
Layout/LineEndStringConcatenationIndentation: # new in 1.18
|
||||
Enabled: true
|
||||
Layout/SpaceBeforeBrackets: # new in 1.7
|
||||
Enabled: true
|
||||
Lint/AmbiguousAssignment: # new in 1.7
|
||||
Enabled: true
|
||||
Lint/AmbiguousOperatorPrecedence: # new in 1.21
|
||||
Enabled: true
|
||||
Lint/AmbiguousRange: # new in 1.19
|
||||
Enabled: true
|
||||
Lint/ConstantOverwrittenInRescue: # new in 1.31
|
||||
Enabled: true
|
||||
Lint/DeprecatedConstants: # new in 1.8
|
||||
Enabled: true
|
||||
Lint/DuplicateBranch: # new in 1.3
|
||||
Enabled: true
|
||||
Lint/DuplicateRegexpCharacterClassElement: # new in 1.1
|
||||
Enabled: true
|
||||
Lint/EmptyBlock: # new in 1.1
|
||||
Enabled: true
|
||||
Lint/EmptyClass: # new in 1.3
|
||||
Enabled: true
|
||||
Lint/EmptyInPattern: # new in 1.16
|
||||
Enabled: true
|
||||
Lint/IncompatibleIoSelectWithFiberScheduler: # new in 1.21
|
||||
Enabled: true
|
||||
Lint/LambdaWithoutLiteralBlock: # new in 1.8
|
||||
Enabled: true
|
||||
Lint/NoReturnInBeginEndBlocks: # new in 1.2
|
||||
Enabled: true
|
||||
Lint/NonAtomicFileOperation: # new in 1.31
|
||||
Enabled: true
|
||||
Lint/NumberedParameterAssignment: # new in 1.9
|
||||
Enabled: true
|
||||
Lint/OrAssignmentToConstant: # new in 1.9
|
||||
Enabled: true
|
||||
Lint/RedundantDirGlobSort: # new in 1.8
|
||||
Enabled: true
|
||||
Lint/RefinementImportMethods: # new in 1.27
|
||||
Enabled: true
|
||||
Lint/RequireRangeParentheses: # new in 1.32
|
||||
Enabled: true
|
||||
Lint/RequireRelativeSelfPath: # new in 1.22
|
||||
Enabled: true
|
||||
Lint/SymbolConversion: # new in 1.9
|
||||
Enabled: true
|
||||
Lint/ToEnumArguments: # new in 1.1
|
||||
Enabled: true
|
||||
Lint/TripleQuotes: # new in 1.9
|
||||
Enabled: true
|
||||
Lint/UnexpectedBlockArity: # new in 1.5
|
||||
Enabled: true
|
||||
Lint/UnmodifiedReduceAccumulator: # new in 1.1
|
||||
Enabled: true
|
||||
Lint/UselessRuby2Keywords: # new in 1.23
|
||||
Enabled: true
|
||||
Naming/BlockForwarding: # new in 1.24
|
||||
Enabled: true
|
||||
Security/CompoundHash: # new in 1.28
|
||||
Enabled: true
|
||||
Security/IoMethods: # new in 1.22
|
||||
Enabled: true
|
||||
Style/ArgumentsForwarding: # new in 1.1
|
||||
Enabled: true
|
||||
Style/CollectionCompact: # new in 1.2
|
||||
Enabled: true
|
||||
Style/DocumentDynamicEvalDefinition: # new in 1.1
|
||||
Enabled: true
|
||||
Style/EmptyHeredoc: # new in 1.32
|
||||
Enabled: true
|
||||
Style/EndlessMethod: # new in 1.8
|
||||
Enabled: true
|
||||
Style/EnvHome: # new in 1.29
|
||||
Enabled: true
|
||||
Style/FetchEnvVar: # new in 1.28
|
||||
Enabled: true
|
||||
Style/FileRead: # new in 1.24
|
||||
Enabled: true
|
||||
Style/FileWrite: # new in 1.24
|
||||
Enabled: true
|
||||
Style/HashConversion: # new in 1.10
|
||||
Enabled: true
|
||||
Style/HashExcept: # new in 1.7
|
||||
Enabled: true
|
||||
Style/IfWithBooleanLiteralBranches: # new in 1.9
|
||||
Enabled: true
|
||||
Style/InPatternThen: # new in 1.16
|
||||
Enabled: true
|
||||
Style/MapCompactWithConditionalBlock: # new in 1.30
|
||||
Enabled: true
|
||||
Style/MapToHash: # new in 1.24
|
||||
Enabled: true
|
||||
Style/MultilineInPatternThen: # new in 1.16
|
||||
Enabled: true
|
||||
Style/NegatedIfElseCondition: # new in 1.2
|
||||
Enabled: true
|
||||
Style/NestedFileDirname: # new in 1.26
|
||||
Enabled: true
|
||||
Style/NilLambda: # new in 1.3
|
||||
Enabled: true
|
||||
Style/NumberedParameters: # new in 1.22
|
||||
Enabled: true
|
||||
Style/NumberedParametersLimit: # new in 1.22
|
||||
Enabled: true
|
||||
Style/ObjectThen: # new in 1.28
|
||||
Enabled: true
|
||||
Style/OpenStructUse: # new in 1.23
|
||||
Enabled: true
|
||||
Style/QuotedSymbols: # new in 1.16
|
||||
Enabled: true
|
||||
Style/RedundantArgument: # new in 1.4
|
||||
Enabled: true
|
||||
Style/RedundantInitialize: # new in 1.27
|
||||
Enabled: true
|
||||
Style/RedundantSelfAssignmentBranch: # new in 1.19
|
||||
Enabled: true
|
||||
Style/SelectByRegexp: # new in 1.22
|
||||
Enabled: true
|
||||
Style/StringChars: # new in 1.12
|
||||
Enabled: true
|
||||
Style/SwapValues: # new in 1.1
|
||||
Enabled: true
|
||||
Performance/AncestorsInclude: # new in 1.7
|
||||
Enabled: true
|
||||
Performance/BigDecimalWithNumericArgument: # new in 1.7
|
||||
Enabled: true
|
||||
Performance/BlockGivenWithExplicitBlock: # new in 1.9
|
||||
Enabled: true
|
||||
Performance/CollectionLiteralInLoop: # new in 1.8
|
||||
Enabled: true
|
||||
Performance/ConcurrentMonotonicTime: # new in 1.12
|
||||
Enabled: true
|
||||
Performance/ConstantRegexp: # new in 1.9
|
||||
Enabled: true
|
||||
Performance/MapCompact: # new in 1.11
|
||||
Enabled: true
|
||||
Performance/MethodObjectAsBlock: # new in 1.9
|
||||
Enabled: true
|
||||
Performance/RedundantEqualityComparisonBlock: # new in 1.10
|
||||
Enabled: true
|
||||
Performance/RedundantSortBlock: # new in 1.7
|
||||
Enabled: true
|
||||
Performance/RedundantSplitRegexpArgument: # new in 1.10
|
||||
Enabled: true
|
||||
Performance/RedundantStringChars: # new in 1.7
|
||||
Enabled: true
|
||||
Performance/ReverseFirst: # new in 1.7
|
||||
Enabled: true
|
||||
Performance/SortReverse: # new in 1.7
|
||||
Enabled: true
|
||||
Performance/Squeeze: # new in 1.7
|
||||
Enabled: true
|
||||
Performance/StringIdentifierArgument: # new in 1.13
|
||||
Enabled: true
|
||||
Performance/StringInclude: # new in 1.7
|
||||
Enabled: true
|
||||
Performance/Sum: # new in 1.8
|
||||
Enabled: true
|
71
.rubocop_todo.yml
Normal file
@ -0,0 +1,71 @@
|
||||
# This configuration was generated by
|
||||
# `rubocop --auto-gen-config`
|
||||
# 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
|
||||
# versions of RuboCop, may require this file to be generated again.
|
||||
|
||||
# Offense count: 6
|
||||
# Configuration parameters: AllowedMethods.
|
||||
# AllowedMethods: enums
|
||||
Lint/ConstantDefinitionInBlock:
|
||||
Exclude:
|
||||
- 'spec/faraday/options/options_spec.rb'
|
||||
- 'spec/faraday/rack_builder_spec.rb'
|
||||
- 'spec/faraday/request/instrumentation_spec.rb'
|
||||
|
||||
# Offense count: 11
|
||||
# Configuration parameters: AllowComments, AllowEmptyLambdas.
|
||||
Lint/EmptyBlock:
|
||||
Exclude:
|
||||
- 'spec/faraday/connection_spec.rb'
|
||||
- 'spec/faraday/rack_builder_spec.rb'
|
||||
- 'spec/faraday/response_spec.rb'
|
||||
|
||||
# Offense count: 13
|
||||
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
|
||||
Metrics/AbcSize:
|
||||
Max: 42
|
||||
|
||||
# Offense count: 3
|
||||
# Configuration parameters: CountComments, CountAsOne.
|
||||
Metrics/ClassLength:
|
||||
Max: 230
|
||||
|
||||
# Offense count: 9
|
||||
# Configuration parameters: AllowedMethods, AllowedPatterns.
|
||||
Metrics/CyclomaticComplexity:
|
||||
Max: 13
|
||||
|
||||
# Offense count: 27
|
||||
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
|
||||
Metrics/MethodLength:
|
||||
Max: 33
|
||||
|
||||
# Offense count: 1
|
||||
# Configuration parameters: CountKeywordArgs, MaxOptionalParameters.
|
||||
Metrics/ParameterLists:
|
||||
Max: 6
|
||||
|
||||
# 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:
|
||||
- 'lib/faraday/connection.rb'
|
||||
- 'lib/faraday/options.rb'
|
47
.travis.yml
@ -1,47 +0,0 @@
|
||||
sudo: false
|
||||
language: ruby
|
||||
script: bundle exec script/test
|
||||
cache: bundler
|
||||
|
||||
rvm:
|
||||
- 1.9.3
|
||||
- 2.0.0
|
||||
- 2.1
|
||||
- 2.2
|
||||
- 2.3
|
||||
- 2.4
|
||||
- 2.5.0
|
||||
- ruby-head
|
||||
- jruby-19mode
|
||||
- jruby-20mode
|
||||
- jruby-21mode
|
||||
- jruby-head
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
# "A fatal error has been detected by the Java Runtime Environment:
|
||||
# Internal Error (sharedRuntime.cpp:843)"
|
||||
- rvm: jruby-19mode
|
||||
- rvm: jruby-20mode
|
||||
- rvm: jruby-21mode
|
||||
- rvm: jruby-head
|
||||
- rvm: ruby-head
|
||||
fast_finish: true
|
||||
|
||||
env:
|
||||
matrix:
|
||||
- SSL=no
|
||||
- SSL=yes
|
||||
global:
|
||||
- JRUBY_OPTS="$JRUBY_OPTS --debug"
|
||||
deploy:
|
||||
provider: rubygems
|
||||
api_key:
|
||||
secure: EqbOu9BQp5jkivJ8qvTo89f3J49KOByBueU3XulrJ2Kqm/ov4RDFsmp9/uHAnSLdmKSkzZaeq79t1AUNfKGX1ZqkKgq/Nw2BKGFnh5ZOjrkrRZR1Vm09OHxqiViEbtg+jZ8VOLY/iDFEkNIzuj9/H3iHGXC0XiKH2LTHOFH63Bs=
|
||||
gem: faraday
|
||||
on:
|
||||
tags: true
|
||||
repo: lostisland/faraday
|
||||
rvm: 2.4
|
||||
condition: '"$SSL" = "yes"'
|
||||
|
12
.yardopts
Normal file
@ -0,0 +1,12 @@
|
||||
--no-private
|
||||
--exclude test
|
||||
--exclude .github
|
||||
--exclude coverage
|
||||
--exclude doc
|
||||
--exclude script
|
||||
--markup markdown
|
||||
--readme README.md
|
||||
|
||||
lib/**/*.rb
|
||||
-
|
||||
CHANGELOG.md
|
554
CHANGELOG.md
@ -1,6 +1,558 @@
|
||||
# Faraday Changelog
|
||||
|
||||
For newer changes, please see https://github.com/lostisland/faraday/releases
|
||||
## The changelog has moved!
|
||||
|
||||
This file is not being updated anymore. Instead, please check the [Releases](https://github.com/lostisland/faraday/releases) page.
|
||||
|
||||
## [2.2.0](https://github.com/lostisland/faraday/compare/v2.1.0...v2.2.0) (2022-02-03)
|
||||
|
||||
* Reintroduce the possibility to register middleware with symbols, strings or procs in [#1391](https://github.com/lostisland/faraday/pull/1391)
|
||||
|
||||
## [2.1.0](https://github.com/lostisland/faraday/compare/v2.0.1...v2.1.0) (2022-01-15)
|
||||
|
||||
* Fix test adapter thread safety by @iMacTia in [#1380](https://github.com/lostisland/faraday/pull/1380)
|
||||
* Add default adapter options by @hirasawayuki in [#1382](https://github.com/lostisland/faraday/pull/1382)
|
||||
* CI: Add Ruby 3.1 to matrix by @petergoldstein in [#1374](https://github.com/lostisland/faraday/pull/1374)
|
||||
* docs: fix regex pattern in logger.md examples by @hirasawayuki in [#1378](https://github.com/lostisland/faraday/pull/1378)
|
||||
|
||||
## [2.0.1](https://github.com/lostisland/faraday/compare/v2.0.0...v2.0.1) (2022-01-05)
|
||||
|
||||
* Re-add `faraday-net_http` as default adapter by @iMacTia in [#1366](https://github.com/lostisland/faraday/pull/1366)
|
||||
* Updated sample format in UPGRADING.md by @vimutter in [#1361](https://github.com/lostisland/faraday/pull/1361)
|
||||
* docs: Make UPGRADING examples more copyable by @olleolleolle in [#1363](https://github.com/lostisland/faraday/pull/1363)
|
||||
|
||||
## [2.0.0](https://github.com/lostisland/faraday/compare/v1.8.0...v2.0.0) (2022-01-04)
|
||||
|
||||
The next major release is here, and it comes almost 2 years after the release of v1.0!
|
||||
|
||||
This release changes the way you use Faraday and embraces a new paradigm of Faraday as an ecosystem, rather than a library.
|
||||
|
||||
What does that mean? It means that Faraday is less of a bundled tool and more of a framework for the community to build on top of.
|
||||
|
||||
As a result, all adapters and some middleware have moved out and are now shipped as standalone gems 🙌!
|
||||
|
||||
But this doesn't mean that upgrading from Faraday 1.x to Faraday 2.0 should be hard, in fact we've listed everything you need to do in the [UPGRADING.md](https://github.com/lostisland/faraday/blob/main/UPGRADING.md) doc.
|
||||
|
||||
Moreover, we've setup a new [awesome-faraday](https://github.com/lostisland/awesome-faraday) repository that will showcase a curated list of adapters and middleware 😎.
|
||||
|
||||
This release was the result of the efforts of the core team and all the contributors, new and old, that have helped achieve this milestone 👏.
|
||||
|
||||
## What's Changed
|
||||
|
||||
* Autoloading, dependency loading and middleware registry cleanup by @iMacTia in [#1301](https://github.com/lostisland/faraday/pull/1301)
|
||||
* Move JSON middleware (request and response) from faraday_middleware by @iMacTia in [#1300](https://github.com/lostisland/faraday/pull/1300)
|
||||
* Remove deprecated `Faraday::Request#method` by @olleolleolle in [#1303](https://github.com/lostisland/faraday/pull/1303)
|
||||
* Remove deprecated `Faraday::UploadIO` by @iMacTia in [#1307](https://github.com/lostisland/faraday/pull/1307)
|
||||
* [1.x] Deprecate Authorization helpers in `Faraday::Connection` by @iMacTia in [#1306](https://github.com/lostisland/faraday/pull/1306)
|
||||
* Drop deprecated auth helpers from Connection and refactor auth middleware by @iMacTia in [#1308](https://github.com/lostisland/faraday/pull/1308)
|
||||
* Add Faraday 1.x examples in authentication.md docs by @iMacTia in [#1320](https://github.com/lostisland/faraday/pull/1320)
|
||||
* Fix passing a URL with embedded basic auth by @iMacTia in [#1324](https://github.com/lostisland/faraday/pull/1324)
|
||||
* Register JSON middleware by @mollerhoj in [#1331](https://github.com/lostisland/faraday/pull/1331)
|
||||
* Retry middleware should handle string exception class name consistently by @jrochkind in [#1334](https://github.com/lostisland/faraday/pull/1334)
|
||||
* Improve request info in exceptions raised by RaiseError Middleware by @willianzocolau in [#1335](https://github.com/lostisland/faraday/pull/1335)
|
||||
* Remove net-http adapter and update docs by @iMacTia in [#1336](https://github.com/lostisland/faraday/pull/1336)
|
||||
* Explain plan for faraday_middleware in UPGRADING.md by @iMacTia in [#1339](https://github.com/lostisland/faraday/pull/1339)
|
||||
* Scripts folder cleanup by @iMacTia in [#1340](https://github.com/lostisland/faraday/pull/1340)
|
||||
* Replace `Hash#merge` with `Utils#deep_merge` for connection options by @xkwd in [#1343](https://github.com/lostisland/faraday/pull/1343)
|
||||
* Callable authorizers by @sled in [#1345](https://github.com/lostisland/faraday/pull/1345)
|
||||
* Default value for exc error by @DariuszMusielak in [#1351](https://github.com/lostisland/faraday/pull/1351)
|
||||
* Don't call `retry_block` unless a retry is going to happen by @jrochkind in [#1350](https://github.com/lostisland/faraday/pull/1350)
|
||||
* Improve documentation for v2 by @iMacTia in [#1353](https://github.com/lostisland/faraday/pull/1353)
|
||||
* Remove default `default_adapter` (yes, you read that right) by @iMacTia in [#1354](https://github.com/lostisland/faraday/pull/1354)
|
||||
* Remove retry middleware by @iMacTia in [#1356](https://github.com/lostisland/faraday/pull/1356)
|
||||
* Remove multipart middleware and all its documentation and tests by @iMacTia in [#1357](https://github.com/lostisland/faraday/pull/1357)
|
||||
|
||||
## [1.9.3](https://github.com/lostisland/faraday/compare/v1.9.2...v1.9.3) (2022-01-06)
|
||||
|
||||
* Re-add support for Ruby 2.4+ by @iMacTia in [#1371](https://github.com/lostisland/faraday/pull/1371)
|
||||
|
||||
## [1.9.2](https://github.com/lostisland/faraday/compare/v1.9.1...v1.9.2) (2022-01-06)
|
||||
|
||||
* Add alias with legacy name to gemified middleware by @iMacTia in [#1372](https://github.com/lostisland/faraday/pull/1372)
|
||||
|
||||
## [1.9.1](https://github.com/lostisland/faraday/compare/v1.9.0...v1.9.1) (2022-01-06)
|
||||
|
||||
* Update adapter dependencies in Gemspec by @iMacTia in [#1370](https://github.com/lostisland/faraday/pull/1370)
|
||||
|
||||
## [1.9.0](https://github.com/lostisland/faraday/compare/v1.8.0...v1.9.0) (2022-01-06)
|
||||
|
||||
* Use external multipart and retry middleware by @iMacTia in [#1367](https://github.com/lostisland/faraday/pull/1367)
|
||||
|
||||
## [1.8.0](https://github.com/lostisland/faraday/releases/tag/v1.8.0) (2021-09-18)
|
||||
|
||||
### Features
|
||||
|
||||
* Backport authorization procs (#1322, @jarl-dk)
|
||||
|
||||
## [v1.7.0](https://github.com/lostisland/faraday/releases/tag/v1.7.0) (2021-08-09)
|
||||
|
||||
### Features
|
||||
|
||||
* Add strict_mode to Test::Stubs (#1298, @yykamei)
|
||||
|
||||
## [v1.6.0](https://github.com/lostisland/faraday/releases/tag/v1.6.0) (2021-08-01)
|
||||
|
||||
### Misc
|
||||
|
||||
* Use external Rack adapter (#1296, @iMacTia)
|
||||
|
||||
## [v1.5.1](https://github.com/lostisland/faraday/releases/tag/v1.5.1) (2021-07-11)
|
||||
|
||||
### Fixes
|
||||
|
||||
* Fix JRuby incompatibility after moving out EM adapters (#1294, @ahorek)
|
||||
|
||||
### Documentation
|
||||
|
||||
* Update YARD to follow RackBuilder (#1292, @kachick)
|
||||
|
||||
## [v1.5.0](https://github.com/lostisland/faraday/releases/tag/v1.5.0) (2021-07-04)
|
||||
|
||||
### Misc
|
||||
|
||||
* Use external httpclient adapter (#1289, @iMacTia)
|
||||
* Use external patron adapter (#1290, @iMacTia)
|
||||
|
||||
## [v1.4.3](https://github.com/lostisland/faraday/releases/tag/v1.4.3) (2021-06-24)
|
||||
|
||||
### Fixes
|
||||
|
||||
* Silence warning (#1286, @gurgeous)
|
||||
* Always dup url_prefix in Connection#build_exclusive_url (#1288, @alexeyds)
|
||||
|
||||
## [v1.4.2](https://github.com/lostisland/faraday/releases/tag/v1.4.2) (2021-05-22)
|
||||
|
||||
### Fixes
|
||||
* Add proxy setting when url_prefix is changed (#1276, @ci)
|
||||
* Default proxy scheme to http:// if necessary, fixes #1282 (#1283, @gurgeous)
|
||||
|
||||
### Documentation
|
||||
* Improve introduction page (#1273, @gurgeous)
|
||||
* Docs: add more middleware examples (#1277, @gurgeous)
|
||||
|
||||
### Misc
|
||||
* Use external `em_http` and `em_synchrony` adapters (#1274, @iMacTia)
|
||||
|
||||
## [v1.4.1](https://github.com/lostisland/faraday/releases/tag/v1.4.1) (2021-04-18)
|
||||
|
||||
### Fixes
|
||||
|
||||
* Fix dependencies from external adapter gems (#1269, @iMacTia)
|
||||
|
||||
## [v1.4.0](https://github.com/lostisland/faraday/releases/tag/v1.4.0) (2021-04-16)
|
||||
|
||||
### Highlights
|
||||
|
||||
With this release, we continue the work of gradually moving out adapters into their own gems 🎉
|
||||
Thanks to @MikeRogers0 for helping the Faraday team in progressing with this quest 👏
|
||||
|
||||
And thanks to @olleolleolle efforts, Faraday is becoming more inclusive than ever 🤗
|
||||
Faraday's `master` branch has been renamed into `main`, we have an official policy on inclusive language and even a rubocop plugin to check for non-inclusive words ❤️!
|
||||
Checkout the "Misc" section below for more details 🙌 !
|
||||
|
||||
### Fixes
|
||||
|
||||
* Fix NoMethodError undefined method 'coverage' (#1255, @Maroo-b)
|
||||
|
||||
### Documentation
|
||||
|
||||
* Some docs on EventMachine adapters. (#1232, @damau)
|
||||
* CONTRIBUTING: Fix grammar and layout (#1261, @olleolleolle)
|
||||
|
||||
### Misc
|
||||
|
||||
* Replacing Net::HTTP::Persistent with faraday-net_http_persistent (#1250, @MikeRogers0)
|
||||
* CI: Configure the regenerated Coveralls token (#1256, @olleolleolle)
|
||||
* Replace Excon adapter with Faraday::Excon gem, and fix autoloading issue with Faraday::NetHttpPersistent (#1257, @iMacTia)
|
||||
* Drop CodeClimate (#1259, @olleolleolle)
|
||||
* CI: Rename default branch to main (#1263, @olleolleolle)
|
||||
* Drop RDoc support file .document (#1264, @olleolleolle, @iMacTia)
|
||||
* CONTRIBUTING: add a policy on inclusive language (#1262, @olleolleolle)
|
||||
* Add rubocop-inclusivity (#1267, @olleolleolle, @iMacTia)
|
||||
|
||||
## [v1.3.1](https://github.com/lostisland/faraday/releases/tag/v1.3.1) (2021-04-16)
|
||||
|
||||
### Fixes
|
||||
|
||||
* Escape colon in path segment (#1237, @yarafan)
|
||||
* Handle IPv6 address String on Faraday::Connection#proxy_from_env (#1252, @cosmo0920)
|
||||
|
||||
### Documentation
|
||||
|
||||
* Fix broken Rubydoc.info links (#1236, @nickcampbell18)
|
||||
* Add httpx to list of external adapters (#1246, @HoneyryderChuck)
|
||||
|
||||
### Misc
|
||||
|
||||
* Refactor CI to remove duplicated line (#1230, @tricknotes)
|
||||
* Gemspec: Pick a good ruby2_keywords release (#1241, @olleolleolle)
|
||||
|
||||
## [v1.3.0](https://github.com/lostisland/faraday/releases/tag/v1.3.0) (2020-12-31)
|
||||
|
||||
### Highlights
|
||||
Faraday v1.3.0 is the first release to officially support Ruby 3.0 in the CI pipeline 🎉 🍾!
|
||||
|
||||
This is also the first release with a previously "included" adapter (Net::HTTP) being isolated into a [separate gem](https://github.com/lostisland/faraday-net_http) 🎊!
|
||||
The new adapter is added to Faraday as a dependency for now, so that means full backwards-compatibility, but just to be safe be careful when upgrading!
|
||||
|
||||
This is a huge step towards are Faraday v2.0 objective of pushing adapters and middleware into separate gems.
|
||||
Many thanks to the Faraday Team, @JanDintel and everyone who attended the [ROSS Conf remote event](https://www.rossconf.io/event/remote/)
|
||||
|
||||
### Features
|
||||
|
||||
* Improves consistency with Faraday::Error and Faraday::RaiseError (#1229, @qsona, @iMacTia)
|
||||
|
||||
### Fixes
|
||||
|
||||
* Don't assign to global ::Timer (#1227, @bpo)
|
||||
|
||||
### Documentation
|
||||
|
||||
* CHANGELOG: add releases after 1.0 (#1225, @olleolleolle)
|
||||
* Improves retry middleware documentation. (#1228, @iMacTia)
|
||||
|
||||
### Misc
|
||||
|
||||
* Move out Net::HTTP adapter (#1222, @JanDintel, @iMacTia)
|
||||
* Adds Ruby 3.0 to CI Matrix (#1226, @iMacTia)
|
||||
|
||||
|
||||
## [v1.2.0](https://github.com/lostisland/faraday/releases/tag/v1.2.0) (2020-12-23)
|
||||
|
||||
### Features
|
||||
|
||||
* Introduces `on_request` and `on_complete` methods in `Faraday::Middleware`. (#1194, @iMacTia)
|
||||
|
||||
### Fixes
|
||||
|
||||
* Require 'date' to avoid retry exception (#1206, @rustygeldmacher)
|
||||
* Fix rdebug recursion issue (#1205, @native-api)
|
||||
* Update call to `em_http_ssl_patch` (#1202, @kylekeesling)
|
||||
* `EmHttp` adapter: drop superfluous loaded? check (#1213, @olleolleolle)
|
||||
* Avoid 1 use of keyword hackery (#1211, @grosser)
|
||||
* Fix #1219 `Net::HTTP` still uses env proxy (#1221, @iMacTia)
|
||||
|
||||
### Documentation
|
||||
|
||||
* Add comment in gemspec to explain exposure of `examples` and `spec` folders. (#1192, @iMacTia)
|
||||
* Adapters, how to create them (#1193, @olleolleolle)
|
||||
* Update documentation on using the logger (#1196, @tijmenb)
|
||||
* Adjust the retry documentation and spec to align with implementation (#1198, @nbeyer)
|
||||
|
||||
### Misc
|
||||
|
||||
* Test against ruby head (#1208, @grosser)
|
||||
|
||||
## [v1.1.0](https://github.com/lostisland/faraday/releases/tag/v1.1.0) (2020-10-17)
|
||||
|
||||
### Features
|
||||
|
||||
* Makes parameters sorting configurable (#1162 @wishdev)
|
||||
* Introduces `flat_encode` option for multipart adapter. (#1163 @iMacTia)
|
||||
* Include request info in exceptions raised by RaiseError Middleware (#1181 @SandroDamilano)
|
||||
|
||||
### Fixes
|
||||
|
||||
* Avoid `last arg as keyword param` warning when building user middleware on Ruby 2.7 (#1153 @dgholz)
|
||||
* Limits net-http-persistent version to < 4.0 (#1156 @iMacTia)
|
||||
* Update `typhoeus` to new stable version (`1.4`) (#1159 @AlexWayfer)
|
||||
* Properly fix test failure with Rack 2.1+. (#1171 @voxik)
|
||||
|
||||
### Documentation
|
||||
|
||||
* Improves documentation on how to contribute to the site by using Docker. (#1175 @iMacTia)
|
||||
* Remove retry_change_requests from documentation (#1185 @stim371)
|
||||
|
||||
### Misc
|
||||
|
||||
* Link from GitHub Actions badge to CI workflow (#1141 @olleolleolle)
|
||||
* Return tests of `Test` adapter (#1147 @AlexWayfer)
|
||||
* Add 1.0 release to wording in CONTRIBUTING (#1155 @olleolleolle)
|
||||
* Fix linting bumping Rubocop to 0.90.0 (#1182 @iMacTia)
|
||||
* Drop `git ls-files` in gemspec (#1183 @utkarsh2102)
|
||||
* Upgrade CI to ruby/setup-ruby (#1187 @gogainda)
|
||||
|
||||
## [v1.0.1](https://github.com/lostisland/faraday/releases/tag/v1.0.1) (2020-03-29)
|
||||
|
||||
### Fixes
|
||||
|
||||
* Use Net::HTTP#start(&block) to ensure closed TCP connections (#1117)
|
||||
* Fully qualify constants to be checked (#1122)
|
||||
* Allows `parse` method to be private/protected in response middleware (#1123)
|
||||
* Encode Spaces in Query Strings as '%20' Instead of '+' (#1125)
|
||||
* Limits rack to v2.0.x (#1127)
|
||||
* Adapter Registry reads also use mutex (#1136)
|
||||
|
||||
### Documentation
|
||||
|
||||
* Retry middleware documentation fix (#1109)
|
||||
* Docs(retry): precise usage of retry-after (#1111)
|
||||
* README: Link the logo to the website (#1112)
|
||||
* Website: add search bar (#1116)
|
||||
* Fix request/response mix-up in docs text (#1132)
|
||||
|
||||
## [v1.0](https://github.com/lostisland/faraday/releases/tag/v1.0.0) (2020-01-22)
|
||||
|
||||
Features:
|
||||
|
||||
* Add #trace support to Faraday::Connection #861 (@technoweenie)
|
||||
* Add the log formatter that is easy to override and safe to inherit #889 (@prikha)
|
||||
* Support standalone adapters #941 (@iMacTia)
|
||||
* Introduce Faraday::ConflictError for 409 response code #979 (@lucasmoreno)
|
||||
* Add support for setting `read_timeout` option separately #1003 (@springerigor)
|
||||
* Refactor and cleanup timeout settings across adapters #1022 (@technoweenie)
|
||||
* Create ParamPart class to allow multipart posts with JSON content and file upload at the same time #1017 (@jeremy-israel)
|
||||
* Copy UploadIO const -> FilePart for consistency with ParamPart #1018, #1021 (@technoweenie)
|
||||
* Implement streaming responses in the Excon adapter #1026 (@technoweenie)
|
||||
* Add default implementation of `Middleware#close`. #1069 (@ioquatix)
|
||||
* Add `Adapter#close` so that derived classes can call super. #1091 (@ioquatix)
|
||||
* Add log_level option to logger default formatter #1079 (@amrrbakry)
|
||||
* Fix empty array for FlatParamsEncoder `{key: []} -> "key="` #1084 (@mrexox)
|
||||
|
||||
Bugs:
|
||||
|
||||
* Explicitly require date for DateTime library in Retry middleware #844 (@nickpresta)
|
||||
* Refactor Adapter as final endpoints #846 (@iMacTia)
|
||||
* Separate Request and Response bodies in Faraday::Env #847 (@iMacTia)
|
||||
* Implement Faraday::Connection#options to make HTTP requests with the OPTIONS verb. #857 (@technoweenie)
|
||||
* Multipart: Drop Ruby 1.8 String behavior compat #892 (@olleolleolle)
|
||||
* Fix Ruby warnings in Faraday::Options.memoized #962 (@technoweenie)
|
||||
* Allow setting min/max SSL version for a Net::HTTP::Persistent connection #972, #973 (@bdewater, @olleolleolle)
|
||||
* Fix instances of frozen empty string literals #1040 (@BobbyMcWho)
|
||||
* remove temp_proxy and improve proxy tests #1063 (@technoweenie)
|
||||
* improve error initializer consistency #1095 (@technoweenie)
|
||||
|
||||
Misc:
|
||||
|
||||
* Convert minitest suite to RSpec #832 (@iMacTia, with help from @gaynetdinov, @Insti, @technoweenie)
|
||||
* Major effort to update code to RuboCop standards. #854 (@olleolleolle, @iMacTia, @technoweenie, @htwroclau, @jherdman, @Drenmi, @Insti)
|
||||
* Rubocop #1044, #1047 (@BobbyMcWho, @olleolleolle)
|
||||
* Documentation tweaks (@adsteel, @Hubro, @iMacTia, @olleolleolle, @technoweenie)
|
||||
* Update license year #981 (@Kevin-Kawai)
|
||||
* Configure Jekyll plugin jekyll-remote-theme to support Docker usage #999 (@Lewiscowles1986)
|
||||
* Fix Ruby 2.7 warnings #1009 (@tenderlove)
|
||||
* Cleanup adapter connections #1023 (@technoweenie)
|
||||
* Describe clearing cached stubs #1045 (@viraptor)
|
||||
* Add project metadata to the gemspec #1046 (@orien)
|
||||
|
||||
## v0.17.4
|
||||
|
||||
Fixes:
|
||||
|
||||
* NetHttp adapter: wrap Errno::EADDRNOTAVAIL (#1114, @embs)
|
||||
* Fix === for subclasses of deprecated classes (#1243, @mervync)
|
||||
|
||||
## v0.17.3
|
||||
|
||||
Fixes:
|
||||
|
||||
* Reverts changes in error classes hierarchy. #1092 (@iMacTia)
|
||||
* Fix Ruby 1.9 syntax errors and improve Error class testing #1094 (@BanzaiMan,
|
||||
@mrexox, @technoweenie)
|
||||
|
||||
Misc:
|
||||
|
||||
* Stops using `&Proc.new` for block forwarding. #1083 (@olleolleolle)
|
||||
* Update CI to test against ruby 2.0-2.7 #1087, #1099 (@iMacTia, @olleolleolle,
|
||||
@technoweenie)
|
||||
* require FARADAY_DEPRECATE=warn to show Faraday v1.0 deprecation warnings
|
||||
#1098 (@technoweenie)
|
||||
|
||||
## v0.17.1
|
||||
|
||||
Final release before Faraday v1.0, with important fixes for Ruby 2.7.
|
||||
|
||||
Fixes:
|
||||
|
||||
* RaiseError response middleware raises exception if HTTP client returns a nil
|
||||
status. #1042 (@jonnyom, @BobbyMcWho)
|
||||
|
||||
Misc:
|
||||
|
||||
* Fix Ruby 2.7 warnings (#1009)
|
||||
* Add `Faraday::Deprecate` to warn about upcoming v1.0 changes. (#1054, #1059,
|
||||
#1076, #1077)
|
||||
* Add release notes up to current in CHANGELOG.md (#1066)
|
||||
* Port minimal rspec suite from main branch to run backported tests. (#1058)
|
||||
|
||||
## v0.17.0
|
||||
|
||||
This release is the same as v0.15.4. It was pushed to cover up releases
|
||||
v0.16.0-v0.16.2.
|
||||
|
||||
## v0.15.4
|
||||
|
||||
* Expose `pool_size` as a option for the NetHttpPersistent adapter (#834)
|
||||
|
||||
## v0.15.3
|
||||
|
||||
* Make Faraday::Request serialisable with Marshal. (#803)
|
||||
* Add DEFAULT_EXCEPTIONS constant to Request::Retry (#814)
|
||||
* Add support for Ruby 2.6 Net::HTTP write_timeout (#824)
|
||||
|
||||
## v0.15.2
|
||||
|
||||
* Prevents `Net::HTTP` adapters to retry request internally by setting `max_retries` to 0 if available (Ruby 2.5+). (#799)
|
||||
* Fixes `NestedParamsEncoder` handling of empty array values (#801)
|
||||
|
||||
## v0.15.1
|
||||
|
||||
* NetHttpPersistent adapter better reuse of SSL connections (#793)
|
||||
* Refactor: inline cached_connection (#797)
|
||||
* Logger middleware: use $stdout instead of STDOUT (#794)
|
||||
* Fix: do not memoize/reuse Patron session (#796)
|
||||
|
||||
Also in this release:
|
||||
|
||||
* Allow setting min/max ssl version for Net::HTTP (#792)
|
||||
* Allow setting min/max ssl version for Excon (#795)
|
||||
|
||||
## v0.15.0
|
||||
|
||||
Features:
|
||||
|
||||
* Added retry block option to retry middleware. (#770)
|
||||
* Retry middleware improvements (honour Retry-After header, retry statuses) (#773)
|
||||
* Improve response logger middleware output (#784)
|
||||
|
||||
Fixes:
|
||||
|
||||
* Remove unused class error (#767)
|
||||
* Fix minor typo in README (#760)
|
||||
* Reuse persistent connections when using net-http-persistent (#778)
|
||||
* Fix Retry middleware documentation (#781)
|
||||
* Returns the http response when giving up on retrying by status (#783)
|
||||
|
||||
## v0.14.0
|
||||
|
||||
Features:
|
||||
|
||||
* Allow overriding env proxy #754 (@iMacTia)
|
||||
* Remove legacy Typhoeus adapter #715 (@olleolleolle)
|
||||
* External Typhoeus Adapter Compatibility #748 (@iMacTia)
|
||||
* Warn about missing adapter when making a request #743 (@antstorm)
|
||||
* Faraday::Adapter::Test stubs now support entire urls (with host) #741 (@erik-escobedo)
|
||||
|
||||
Fixes:
|
||||
|
||||
* If proxy is manually provided, this takes priority over `find_proxy` #724 (@iMacTia)
|
||||
* Fixes the behaviour for Excon's open_timeout (not setting write_timeout anymore) #731 (@apachelogger)
|
||||
* Handle all connection timeout messages in Patron #687 (@stayhero)
|
||||
|
||||
## v0.13.1
|
||||
|
||||
* Fixes an incompatibility with Addressable::URI being used as uri_parser
|
||||
|
||||
## v0.13.0
|
||||
|
||||
Features:
|
||||
|
||||
* Dynamically reloads the proxy when performing a request on an absolute domain (#701)
|
||||
* Adapter support for Net::HTTP::Persistent v3.0.0 (#619)
|
||||
|
||||
Fixes:
|
||||
|
||||
* Prefer #hostname over #host. (#714)
|
||||
* Fixes an edge-case issue with response headers parsing (missing HTTP header) (#719)
|
||||
|
||||
## v0.12.2
|
||||
|
||||
* Parse headers from aggregated proxy requests/responses (#681)
|
||||
* Guard against invalid middleware configuration with warning (#685)
|
||||
* Do not use :insecure option by default in Patron (#691)
|
||||
* Fixes an issue with HTTPClient not raising a `Faraday::ConnectionFailed` (#702)
|
||||
* Fixes YAML serialization/deserialization for `Faraday::Utils::Headers` (#690)
|
||||
* Fixes an issue with Options having a nil value (#694)
|
||||
* Fixes an issue with Faraday.default_connection not using Faraday.default_connection_options (#698)
|
||||
* Fixes an issue with Options.merge! and Faraday instrumentation middleware (#710)
|
||||
|
||||
## v0.12.1
|
||||
|
||||
* Fix an issue with Patron tests failing on jruby
|
||||
* Fix an issue with new `rewind_files` feature that was causing an exception when the body was not an Hash
|
||||
* Expose wrapped_exception in all client errors
|
||||
* Add Authentication Section to the ReadMe
|
||||
|
||||
## v0.12.0.1
|
||||
|
||||
* Hotfix release to address an issue with TravisCI deploy on Rubygems
|
||||
|
||||
## v0.12.0
|
||||
|
||||
Features:
|
||||
|
||||
* Proxy feature now relies on Ruby `URI::Generic#find_proxy` and can use `no_proxy` ENV variable (not compatible with ruby < 2.0)
|
||||
* Adds support for `context` request option to pass arbitrary information to middlewares
|
||||
|
||||
Fixes:
|
||||
|
||||
* Fix an issue with options that was causing new options to override defaults ones unexpectedly
|
||||
* Rewind `UploadIO`s on retry to fix a compatibility issue
|
||||
* Make multipart boundary unique
|
||||
* Improvements in `README.md`
|
||||
|
||||
## v0.11.0
|
||||
|
||||
Features:
|
||||
|
||||
* Add `filter` method to Logger middleware
|
||||
* Add support for Ruby2.4 and Minitest 6
|
||||
* Introduce block syntax to customise the adapter
|
||||
|
||||
Fixes:
|
||||
|
||||
* Fix an issue that was allowing to override `default_connection_options` from a connection instance
|
||||
* Fix a bug that was causing newline escape characters ("\n") to be used when building the Authorization header
|
||||
|
||||
## v0.10.1
|
||||
|
||||
- Fix an issue with HTTPClient adapter that was causing the SSL to be reset on every request
|
||||
- Rescue `IOError` instead of specific subclass
|
||||
- `Faraday::Utils::Headers` can now be successfully serialised in YAML
|
||||
- Handle `default_connection_options` set with hash
|
||||
|
||||
## v0.10.0
|
||||
|
||||
Breaking changes:
|
||||
- Drop support for Ruby 1.8
|
||||
|
||||
Features:
|
||||
- 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
|
||||
|
||||
Fixes:
|
||||
- Add `Faraday.respond_to?` to find methods managed by `method_missing`
|
||||
- em-http: `request.host` instead of `connection.host` should be taken for SSL validations
|
||||
- Allow `default_connection_options` to be merged when options are passed as url parameter
|
||||
- Improve splitting key-value pairs in raw HTTP headers
|
||||
|
||||
## v0.9.2
|
||||
|
||||
Adapters:
|
||||
- Enable gzip compression for httpclient
|
||||
- Fixes default certificate store for httpclient not having default paths.
|
||||
- Make excon adapter compatible with 0.44 excon version
|
||||
- Add compatibility with Patron 0.4.20
|
||||
- Determine default port numbers in Net::HTTP adapters (Addressable compatibility)
|
||||
- em-http: wrap "connection closed by server" as ConnectionFailed type
|
||||
- Wrap Errno::ETIMEDOUT in Faraday::Error::TimeoutError
|
||||
|
||||
Utils:
|
||||
- Add Rack-compatible support for parsing `a[][b]=c` nested queries
|
||||
- Encode nil values in queries different than empty strings. Before: `a=`; now: `a`.
|
||||
- Have `Faraday::Utils::Headers#replace` clear internal key cache
|
||||
- Dup the internal key cache when a Headers hash is copied
|
||||
|
||||
Env and middleware:
|
||||
- Ensure `env` stored on middleware response has reference to the response
|
||||
- Ensure that Response properties are initialized during `on_complete` (VCR compatibility)
|
||||
- Copy request options in Faraday::Connection#dup
|
||||
- Env custom members should be copied by Env.from(env)
|
||||
- Honour per-request `request.options.params_encoder`
|
||||
- Fix `interval_randomness` data type for Retry middleware
|
||||
- Add maximum interval option for Retry middleware
|
||||
|
||||
## v0.9.1
|
||||
|
||||
|
42
Gemfile
@ -1,27 +1,29 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
source 'https://rubygems.org'
|
||||
|
||||
ruby RUBY_VERSION
|
||||
# Even though we don't officially support JRuby, this dependency makes Faraday
|
||||
# compatible with it, so we're leaving it in for jruby users to use it.
|
||||
gem 'jruby-openssl', '~> 0.11.0', platforms: :jruby
|
||||
|
||||
gem 'ffi-ncurses', '~> 0.3', :platforms => :jruby
|
||||
gem 'jruby-openssl', '~> 0.8.8', :platforms => :jruby
|
||||
gem 'rake'
|
||||
|
||||
group :test do
|
||||
gem 'coveralls', :require => false
|
||||
gem 'em-http-request', '>= 1.1', :require => 'em-http'
|
||||
gem 'em-synchrony', '>= 1.0.3', :require => ['em-synchrony', 'em-synchrony/em-http']
|
||||
gem 'addressable', '< 2.4.0'
|
||||
gem 'excon', '>= 0.27.4'
|
||||
gem 'httpclient', '>= 2.2'
|
||||
gem 'mime-types', '~> 1.25', :platforms => [:jruby, :ruby_18]
|
||||
gem 'minitest', '>= 5.0.5'
|
||||
gem 'net-http-persistent'
|
||||
gem 'patron', '>= 0.4.2', :platforms => :ruby
|
||||
gem 'rack-test', '>= 0.6', :require => 'rack/test'
|
||||
gem 'rest-client', '~> 1.6.0', :platforms => [:jruby, :ruby_18]
|
||||
group :development, :test do
|
||||
gem 'bake-test-external'
|
||||
gem 'coveralls_reborn', require: false
|
||||
gem 'pry'
|
||||
gem 'rack', '~> 3.0'
|
||||
gem 'rake'
|
||||
gem 'rspec', '~> 3.7'
|
||||
gem 'rspec_junit_formatter', '~> 0.4'
|
||||
gem 'simplecov'
|
||||
gem 'sinatra', '~> 1.3'
|
||||
gem 'typhoeus', '~> 1.3', :require => 'typhoeus'
|
||||
gem 'webmock', '~> 3.4'
|
||||
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'
|
||||
gem 'yard-junk'
|
||||
end
|
||||
|
||||
gemspec
|
||||
|
@ -1,4 +1,4 @@
|
||||
Copyright (c) 2009-2017 Rick Olson, Zack Hobson
|
||||
Copyright (c) 2009-2023 Rick Olson, Zack Hobson
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
|
382
README.md
@ -1,353 +1,46 @@
|
||||
# Faraday
|
||||
# [][website]
|
||||
|
||||
[](https://rubygems.org/gems/faraday)
|
||||
[](https://travis-ci.org/lostisland/faraday)
|
||||
[](https://coveralls.io/github/lostisland/faraday?branch=master)
|
||||
[](https://codeclimate.com/github/lostisland/faraday)
|
||||
[](https://gitter.im/lostisland/faraday?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||
[](https://github.com/lostisland/faraday/actions?query=workflow%3ACI)
|
||||
[](https://github.com/lostisland/faraday/discussions)
|
||||
|
||||
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.
|
||||
Take a look at [Awesome Faraday][awesome] for a list of available adapters and middleware.
|
||||
|
||||
Faraday is an HTTP client lib 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.
|
||||
## Why use Faraday?
|
||||
|
||||
Faraday supports these adapters out of the box:
|
||||
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.
|
||||
|
||||
* [Net::HTTP][net_http] _(default)_
|
||||
* [Net::HTTP::Persistent][persistent]
|
||||
* [Excon][]
|
||||
* [Patron][]
|
||||
* [EventMachine][]
|
||||
* [HTTPClient][]
|
||||
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!
|
||||
|
||||
Adapters are slowly being moved into their own gems, or bundled with HTTP clients:
|
||||
## Getting Started
|
||||
|
||||
* [Typhoeus][]
|
||||
The best starting point is the [Faraday Website][website], with its introduction and explanation.
|
||||
|
||||
It also includes a Rack adapter for hitting loaded Rack applications through
|
||||
Rack::Test, and a Test adapter for stubbing requests by hand.
|
||||
|
||||
## API documentation
|
||||
|
||||
Available at [rubydoc.info](http://www.rubydoc.info/gems/faraday).
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Use
|
||||
|
||||
```ruby
|
||||
response = Faraday.get 'http://sushi.com/nigiri/sake.json'
|
||||
```
|
||||
A simple `get` request can be performed by using the syntax described above. This works if you don't need to set up anything; you can roll with just the default middleware
|
||||
stack and default adapter (see [Faraday::RackBuilder#initialize](https://github.com/lostisland/faraday/blob/master/lib/faraday/rack_builder.rb)).
|
||||
|
||||
A more flexible way to use Faraday is to start with a Connection object. If you want to keep the same defaults, you can use this syntax:
|
||||
|
||||
```ruby
|
||||
conn = Faraday.new(:url => 'http://www.example.com')
|
||||
response = conn.get '/users' # GET http://www.example.com/users'
|
||||
```
|
||||
|
||||
Connections can also take an options hash as a parameter or be configured by using a block. Checkout the section called [Advanced middleware usage](#advanced-middleware-usage) for more details about how to use this block for configurations.
|
||||
Since the default middleware stack uses url\_encoded middleware and default adapter, use them on building your own middleware stack.
|
||||
|
||||
```ruby
|
||||
conn = Faraday.new(:url => 'http://sushi.com') do |faraday|
|
||||
faraday.request :url_encoded # form-encode POST params
|
||||
faraday.response :logger # log requests to $stdout
|
||||
faraday.adapter Faraday.default_adapter # make requests with Net::HTTP
|
||||
end
|
||||
|
||||
# Filter sensitive information from logs with a regex matcher
|
||||
|
||||
conn = Faraday.new(:url => 'http://sushi.com/api_key=s3cr3t') do |faraday|
|
||||
faraday.request :url_encoded # form-encode POST params
|
||||
faraday.response :logger do | logger |
|
||||
logger.filter(/(api_key=)(\w+)/,'\1[REMOVED]')
|
||||
end
|
||||
faraday.adapter Faraday.default_adapter # make requests with Net::HTTP
|
||||
end
|
||||
```
|
||||
|
||||
Once you have the connection object, use it to make HTTP requests. You can pass parameters to it in a few different ways:
|
||||
|
||||
```ruby
|
||||
## GET ##
|
||||
|
||||
response = conn.get '/nigiri/sake.json' # GET http://sushi.com/nigiri/sake.json
|
||||
response.body
|
||||
|
||||
conn.get '/nigiri', { :name => 'Maguro' } # GET http://sushi.com/nigiri?name=Maguro
|
||||
|
||||
conn.get do |req| # GET http://sushi.com/search?page=2&limit=100
|
||||
req.url '/search', :page => 2
|
||||
req.params['limit'] = 100
|
||||
end
|
||||
|
||||
## POST ##
|
||||
|
||||
conn.post '/nigiri', { :name => 'Maguro' } # POST "name=maguro" to http://sushi.com/nigiri
|
||||
```
|
||||
|
||||
Some configuration options can be adjusted per request:
|
||||
|
||||
```ruby
|
||||
# post payload as JSON instead of "www-form-urlencoded" encoding:
|
||||
conn.post do |req|
|
||||
req.url '/nigiri'
|
||||
req.headers['Content-Type'] = 'application/json'
|
||||
req.body = '{ "name": "Unagi" }'
|
||||
end
|
||||
|
||||
## Per-request options ##
|
||||
|
||||
conn.get do |req|
|
||||
req.url '/search'
|
||||
req.options.timeout = 5 # open/read timeout in seconds
|
||||
req.options.open_timeout = 2 # connection open timeout in seconds
|
||||
end
|
||||
```
|
||||
|
||||
And you can inject arbitrary data into the request using the `context` option:
|
||||
|
||||
```ruby
|
||||
# Anything you inject using context option will be available in the env on all middlewares
|
||||
|
||||
conn.get do |req|
|
||||
req.url '/search'
|
||||
req.options.context = {
|
||||
foo: 'foo',
|
||||
bar: 'bar'
|
||||
}
|
||||
end
|
||||
```
|
||||
|
||||
### Changing how parameters are serialized
|
||||
|
||||
Sometimes you need to send the same URL parameter multiple times with different
|
||||
values. This requires manually setting the parameter encoder and can be done on
|
||||
either per-connection or per-request basis.
|
||||
|
||||
```ruby
|
||||
# per-connection setting
|
||||
conn = Faraday.new :request => { :params_encoder => Faraday::FlatParamsEncoder }
|
||||
|
||||
conn.get do |req|
|
||||
# per-request setting:
|
||||
# req.options.params_encoder = my_encoder
|
||||
req.params['roll'] = ['california', 'philadelphia']
|
||||
end
|
||||
# GET 'http://sushi.com?roll=california&roll=philadelphia'
|
||||
```
|
||||
|
||||
The value of Faraday `params_encoder` can be any object that responds to:
|
||||
|
||||
* `encode(hash) #=> String`
|
||||
* `decode(string) #=> Hash`
|
||||
|
||||
The encoder will affect both how query strings are processed and how POST bodies
|
||||
get serialized. The default encoder is Faraday::NestedParamsEncoder.
|
||||
|
||||
## Authentication
|
||||
|
||||
Basic and Token authentication are handled by Faraday::Request::BasicAuthentication and Faraday::Request::TokenAuthentication respectively. These can be added as middleware manually or through the helper methods.
|
||||
|
||||
```ruby
|
||||
Faraday.new(...) do |conn|
|
||||
conn.basic_auth('username', 'password')
|
||||
end
|
||||
|
||||
Faraday.new(...) do |conn|
|
||||
conn.token_auth('authentication-token')
|
||||
end
|
||||
```
|
||||
|
||||
## Proxy
|
||||
|
||||
Faraday will try to automatically infer the proxy settings from your system using `URI#find_proxy`.
|
||||
This will retrieve them from environment variables such as http_proxy, ftp_proxy, no_proxy, etc.
|
||||
If for any reason you want to disable this behaviour, you can do so by setting the global varibale `ignore_env_proxy`:
|
||||
|
||||
```ruby
|
||||
Faraday.ignore_env_proxy = true
|
||||
```
|
||||
|
||||
You can also specify a custom proxy when initializing the connection
|
||||
|
||||
```ruby
|
||||
Faraday.new('http://www.example.com', :proxy => 'http://proxy.com')
|
||||
```
|
||||
|
||||
## Advanced middleware usage
|
||||
|
||||
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, so that must be the adapter.
|
||||
|
||||
```ruby
|
||||
Faraday.new(...) do |conn|
|
||||
# POST/PUT params encoders:
|
||||
conn.request :multipart
|
||||
conn.request :url_encoded
|
||||
|
||||
# Last middleware must be the adapter:
|
||||
conn.adapter :net_http
|
||||
end
|
||||
```
|
||||
|
||||
This request middleware setup affects POST/PUT requests in the following way:
|
||||
|
||||
1. `Request::Multipart` checks for files in the payload, otherwise leaves
|
||||
everything untouched;
|
||||
2. `Request::UrlEncoded` encodes as "application/x-www-form-urlencoded" if not
|
||||
already encoded or of another type
|
||||
|
||||
Swapping middleware means giving the other priority. Specifying the
|
||||
"Content-Type" for the request is explicitly stating which middleware should
|
||||
process it.
|
||||
|
||||
Examples:
|
||||
|
||||
```ruby
|
||||
# uploading a file:
|
||||
payload[:profile_pic] = Faraday::UploadIO.new('/path/to/avatar.jpg', 'image/jpeg')
|
||||
|
||||
# "Multipart" middleware detects files and encodes with "multipart/form-data":
|
||||
conn.put '/profile', payload
|
||||
```
|
||||
|
||||
## Writing middleware
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
## 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 below for some examples.
|
||||
|
||||
### NetHttp
|
||||
```ruby
|
||||
conn = Faraday.new(...) do |f|
|
||||
f.adapter :net_http do |http| # yields Net::HTTP
|
||||
http.idle_timeout = 100
|
||||
http.verify_callback = lambda do | preverify_ok, cert_store |
|
||||
# do something here...
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### NetHttpPersistent
|
||||
```ruby
|
||||
conn = Faraday.new(...) do |f|
|
||||
f.adapter :net_http_persistent, pool_size: 5 do |http| # yields Net::HTTP::Persistent
|
||||
http.idle_timeout = 100
|
||||
http.retry_change_requests = true
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### Patron
|
||||
```ruby
|
||||
conn = Faraday.new(...) do |f|
|
||||
f.adapter :patron do |session| # yields Patron::Session
|
||||
session.max_redirects = 10
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### HTTPClient
|
||||
```ruby
|
||||
conn = Faraday.new(...) do |f|
|
||||
f.adapter :httpclient do |client| # yields HTTPClient
|
||||
client.keep_alive_timeout = 20
|
||||
client.ssl_config.timeout = 25
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## Using Faraday for testing
|
||||
|
||||
```ruby
|
||||
# It's possible to define stubbed request outside a test adapter block.
|
||||
stubs = Faraday::Adapter::Test::Stubs.new do |stub|
|
||||
stub.get('/tamago') { |env| [200, {}, 'egg'] }
|
||||
end
|
||||
|
||||
# You can pass stubbed request to the test adapter or define them in a block
|
||||
# or a combination of the two.
|
||||
test = Faraday.new do |builder|
|
||||
builder.adapter :test, stubs do |stub|
|
||||
stub.get('/ebi') { |env| [ 200, {}, 'shrimp' ]}
|
||||
end
|
||||
end
|
||||
|
||||
# It's also possible to stub additional requests after the connection has
|
||||
# been initialized. This is useful for testing.
|
||||
stubs.get('/uni') { |env| [ 200, {}, 'urchin' ]}
|
||||
|
||||
resp = test.get '/tamago'
|
||||
resp.body # => 'egg'
|
||||
resp = test.get '/ebi'
|
||||
resp.body # => 'shrimp'
|
||||
resp = test.get '/uni'
|
||||
resp.body # => 'urchin'
|
||||
resp = test.get '/else' #=> raises "no such stub" error
|
||||
|
||||
# If you like, you can treat your stubs as mocks by verifying that all of
|
||||
# the stubbed calls were made. NOTE that this feature is still fairly
|
||||
# experimental: It will not verify the order or count of any stub, only that
|
||||
# it was called once during the course of the test.
|
||||
stubs.verify_stubbed_calls
|
||||
```
|
||||
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][travis] the following Ruby
|
||||
implementations:
|
||||
|
||||
* Ruby 1.9.3+
|
||||
* [JRuby][] 1.7+
|
||||
* [Rubinius][] 2+
|
||||
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 3.0+
|
||||
|
||||
If something doesn't work on one of these Ruby versions, it's a bug.
|
||||
|
||||
This library may inadvertently work (or seem to work) on other Ruby
|
||||
implementations, however support will only be provided for the versions listed
|
||||
implementations and versions, however support will only be provided for the versions listed
|
||||
above.
|
||||
|
||||
If you would like this library to support another Ruby version, you may
|
||||
@ -361,21 +54,14 @@ of a major release, support for that Ruby version may be dropped.
|
||||
|
||||
Do you want to contribute to Faraday?
|
||||
Open the issues page and check for the `help wanted` label!
|
||||
But before you start coding, please read our [Contributing Guide](https://github.com/lostisland/faraday/blob/master/.github/CONTRIBUTING.md)
|
||||
But before you start coding, please read our [Contributing Guide][contributing]
|
||||
|
||||
## Copyright
|
||||
|
||||
Copyright (c) 2009-2017 [Rick Olson](mailto:technoweenie@gmail.com), Zack Hobson.
|
||||
See [LICENSE][] for details.
|
||||
© 2009 - 2023, the Faraday Team. Website and branding design by [Elena Lo Piccolo](https://elelopic.design).
|
||||
|
||||
[net_http]: http://ruby-doc.org/stdlib/libdoc/net/http/rdoc/Net/HTTP.html
|
||||
[persistent]: https://github.com/drbrain/net-http-persistent
|
||||
[travis]: https://travis-ci.org/lostisland/faraday
|
||||
[excon]: https://github.com/excon/excon#readme
|
||||
[patron]: http://toland.github.io/patron/
|
||||
[eventmachine]: https://github.com/igrigorik/em-http-request#readme
|
||||
[httpclient]: https://github.com/nahi/httpclient
|
||||
[typhoeus]: https://github.com/typhoeus/typhoeus/blob/master/lib/typhoeus/adapters/faraday.rb
|
||||
[jruby]: http://jruby.org/
|
||||
[rubinius]: http://rubini.us/
|
||||
[license]: LICENSE.md
|
||||
[awesome]: https://github.com/lostisland/awesome-faraday/#adapters
|
||||
[website]: https://lostisland.github.io/faraday
|
||||
[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
|
||||
|
14
Rakefile
@ -1,8 +1,12 @@
|
||||
require 'rake/testtask'
|
||||
# frozen_string_literal: true
|
||||
|
||||
task :default => :test
|
||||
require 'rspec/core/rake_task'
|
||||
require 'bundler'
|
||||
|
||||
desc "Run all tests"
|
||||
task :test do
|
||||
exec 'script/test'
|
||||
Bundler::GemHelper.install_tasks
|
||||
|
||||
RSpec::Core::RakeTask.new(:spec) do |task|
|
||||
task.ruby_opts = %w[-W]
|
||||
end
|
||||
|
||||
task default: :spec
|
||||
|
185
UPGRADING.md
Normal file
@ -0,0 +1,185 @@
|
||||
## Faraday 2.0
|
||||
|
||||
### Adapters have moved!
|
||||
|
||||
With this release, we've officially moved all adapters, except for the `net_http` one, out of Faraday.
|
||||
What that means, is that they won't be available out-of-the-box anymore,
|
||||
and you'll instead need to add them to your Gemfile.
|
||||
|
||||
**NOTE: the `net_http` adapter was originally removed as well in version `2.0`, but quickly reintroduced in version `2.0.1`.
|
||||
We strongly suggest you to skip version `2.0` and instead use version `2.0.1` or greater.**
|
||||
|
||||
#### Why was this decision made?
|
||||
|
||||
We've taken this decision for the following technical reasons:
|
||||
|
||||
* We wanted the Faraday gem to focus on providing a clean, standardized, middleware-stack-based API.
|
||||
* We wanted to free the core team from maintaining all the different adapters, relying more on the community to
|
||||
maintain them based on the broad interest. This will allow the core team to focus on implementing features
|
||||
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 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.
|
||||
|
||||
#### So what will this mean for me?
|
||||
|
||||
We did our best to make this transition as painless as possible for you, so here is what we did.
|
||||
|
||||
* All adapters, except for the `net_http` one, have already been moved out and released as separate gems.
|
||||
They've then been re-added into Faraday's v1 dependencies so that you wouldn't notice.
|
||||
This means that immediately after v2.0 will be released, all the adapters that were previously available will be
|
||||
already compatible with Faraday 2.0!
|
||||
* We've setup an [Awesome Faraday](https://github.com/lostisland/awesome-faraday) repository, where you can find and discover adapters.
|
||||
We also highlighted their unique features and level of compliance with Faraday's features.
|
||||
|
||||
#### That's great! What should I change in my code immediately after upgrading?
|
||||
|
||||
* If you just use the default `net_http` adapter, then you don't need to do anything!
|
||||
* Otherwise, add the corresponding adapter gem to your Gemfile (e.g. `faraday-net_http_persistent`). Then, simply require them after you require `faraday`.
|
||||
```ruby
|
||||
# Gemfile
|
||||
gem 'faraday'
|
||||
gem 'faraday-net_http_persistent'
|
||||
|
||||
# Code
|
||||
require 'faraday'
|
||||
require 'faraday/net_http_persistent'
|
||||
```
|
||||
|
||||
### Faraday Middleware Deprecation
|
||||
|
||||
In case you never used it, [Faraday Middleware](https://github.com/lostisland/faraday_middleware) is a handy collection of middleware that have so far been maintained by the Faraday core team.
|
||||
With Faraday 2.0 focusing on becoming an ecosystem, rather than an out-of-the-box solution, it only made sense to take the same approach for middleware as we did for adapters. For this reason, `faraday_middleware` will not be updated to support Faraday 2.0.
|
||||
Instead, we'll support the transition from centralised-repo collection of middleware to individual middleware gems, effectively replicating what we did with adapters. Each middleware will have its own repository and gem, and it will allow developers to only add the ones they require to their Gemfile.
|
||||
|
||||
So don't fret yet! We're doing our best to support our `faraday_middleware` users out there, so here are the steps we've taken to make this work:
|
||||
* We've promoted the highly popular JSON middleware (both request encoding and response parsing) to a core middleware and it will now be shipped together with Faraday. We expect many `faraday_middleware` users will be able to just stop using the extra dependency thanks to this.
|
||||
* We've created a [faraday-middleware-template](https://github.com/lostisland/faraday-middleware-template) repository that will make creating a new middleware gem simple and straightforward.
|
||||
* We've added a middleware section to the [awesome-faraday](https://github.com/lostisland/awesome-faraday) repo, so you can easily find available middleware when you need it.
|
||||
|
||||
It will obviously take time for some of the middleware in `faraday_middleware` to make its way into a separate gem, so we appreciate this might be an upgrade obstacle for some. However this is part of an effort to focus the core team resources tackling the most requested features.
|
||||
We'll be listening to the community and prioritize middleware that are most used, and will be welcoming contributors who want to become owners of the middleware when these become separate from the `faraday_middleware` repo.
|
||||
|
||||
### Bundled middleware moved out
|
||||
|
||||
Moving middleware into its own gem makes sense not only for `faraday_middleware`, but also for middleware bundled with Faraday.
|
||||
As of v2.0, the `retry` and `multipart` middleware have been moved to separate `faraday-retry` and `faraday-multipart` gems.
|
||||
These have been identified as good candidates due to their complexity and external dependencies.
|
||||
Thanks to this change, we were able to make Faraday 2.0 completely dependency free 🎉 (the only exception being `ruby2_keywords`, which will be necessary only while we keep supporting Ruby 2.6).
|
||||
|
||||
#### So what should I do if I currently use the `retry` or `multipart` middleware?
|
||||
|
||||
Upgrading is pretty simple, because the middleware was simply moved out to external gems.
|
||||
All you need to do is to add them to your gemfile (either `faraday-retry` or `faraday-multipart` and require them before usage:
|
||||
|
||||
```ruby
|
||||
# Gemfile
|
||||
gem 'faraday-multipart'
|
||||
gem 'faraday-retry'
|
||||
|
||||
# Connection initializer
|
||||
require 'faraday/retry'
|
||||
require 'faraday/multipart'
|
||||
|
||||
conn = Faraday.new(url) do |f|
|
||||
f.request :multipart
|
||||
f.request :retry
|
||||
# ...
|
||||
end
|
||||
```
|
||||
|
||||
### Autoloading and dependencies
|
||||
|
||||
Faraday has until now provided and relied on a complex dynamic dependencies system.
|
||||
This would allow to reference classes and require dependencies only when needed (e.g. when running the first request) based
|
||||
on the middleware/adapters used.
|
||||
As part of Faraday v2.0, we've removed all external dependencies, which means we don't really need this anymore.
|
||||
This change should not affect you directly, but if you're registering middleware then be aware of the new syntax:
|
||||
|
||||
```ruby
|
||||
# `name` here can be anything you want.
|
||||
# `klass` is your custom middleware class.
|
||||
# This method can also be called on `Faraday::Adapter`, `Faraday::Request` and `Faraday::Response`
|
||||
Faraday::Middleware.register_middleware(name: klass)
|
||||
```
|
||||
|
||||
The `register_middleware` method also previously allowed to provide a symbol, string, proc, or array
|
||||
but this has been removed from the v2.0 release to simplify the interface.
|
||||
(EDIT: symbol/string/proc have subsequently been reintroduced in v2.2, but not the array).
|
||||
|
||||
### 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/included/authentication).
|
||||
|
||||
For more details, see https://github.com/lostisland/faraday/pull/1306
|
||||
|
||||
### The `dependency` method in middlewares has been removed
|
||||
|
||||
In Faraday 1, a deferred require was used with the `dependency` method.
|
||||
|
||||
In Faraday 2, that method has been removed. In your middleware gems, use a regular `require` at the top of the file,
|
||||
|
||||
### Others
|
||||
|
||||
* Rename `Faraday::Request#method` to `#http_method`.
|
||||
* Remove `Faraday::Response::Middleware`. You can now use the new `on_complete` callback provided by `Faraday::Middleware`.
|
||||
* `Faraday.default_connection_options` will now be deep-merged into new connections to avoid overriding them (e.g. headers).
|
||||
* `Faraday::Builder#build` method is not exposed through `Faraday::Connection` anymore and does not reset the handlers if called multiple times. This method should be used internally only.
|
||||
|
||||
## Faraday 1.0
|
||||
|
||||
### Errors
|
||||
* Removes sub-class constants definition from `Faraday::Error`. A sub-class (e.g. `ClientError`) was previously accessible
|
||||
either through the `Faraday` module (e.g. `Faraday::ClientError`) or through the `Faraday::Error` class (e.g. `Faraday::Error::ClientError`).
|
||||
The latter is no longer available and the former should be used instead, so check your `rescue`s.
|
||||
* Introduces a new `Faraday::ServerError` (5xx status codes) alongside the existing `Faraday::ClientError` (4xx status codes).
|
||||
Please note `Faraday::ClientError` was previously used for both.
|
||||
* Introduces new Errors that describe the most common REST status codes:
|
||||
* Faraday::BadRequestError (400)
|
||||
* Faraday::UnauthorizedError (401)
|
||||
* Faraday::ForbiddenError (403)
|
||||
* Faraday::ProxyAuthError (407). Please note this raised a `Faraday::ConnectionFailed` before.
|
||||
* Faraday::ConflictError (409)
|
||||
* Faraday::UnprocessableEntityError (422)
|
||||
* The following error classes have changed the hierarchy to better mirror their real-world usage and semantic meaning:
|
||||
* TimeoutError < ServerError (was < ClientError)
|
||||
* ConnectionFailed < Error (was < ClientError)
|
||||
* SSLError < Error (was < ClientError)
|
||||
* ParsingError < Error (was < ClientError)
|
||||
* RetriableResponse < Error (was < ClientError)
|
||||
|
||||
### Custom adapters
|
||||
If you have written a custom adapter, please be aware that `env.body` is now an alias to the two new properties `request_body` and `response_body`.
|
||||
This should work without you noticing if your adapter inherits from `Faraday::Adapter` and calls `save_response`, but if it doesn't, then please ensure you set the `status` BEFORE the `body` while processing the response.
|
||||
|
||||
### Others
|
||||
* Dropped support for jruby and Rubinius.
|
||||
* Officially supports Ruby 2.4+
|
||||
* In order to specify the adapter you now MUST use the `#adapter` method on the connection builder. If you don't do so and your adapter inherits from `Faraday::Adapter` then Faraday will raise an exception. Otherwise, Faraday will automatically push the default adapter at the end of the stack causing your request to be executed twice.
|
||||
```ruby
|
||||
class OfficialAdapter < Faraday::Adapter
|
||||
...
|
||||
end
|
||||
|
||||
class MyAdapter
|
||||
...
|
||||
end
|
||||
|
||||
# This will raise an exception
|
||||
conn = Faraday.new(...) do |f|
|
||||
f.use OfficialAdapter
|
||||
end
|
||||
|
||||
# This will cause Faraday inserting the default adapter at the end of the stack
|
||||
conn = Faraday.new(...) do |f|
|
||||
f.use MyAdapter
|
||||
end
|
||||
|
||||
# You MUST use `adapter` method
|
||||
conn = Faraday.new(...) do |f|
|
||||
f.adapter AnyAdapter
|
||||
end
|
||||
```
|
15
bin/console
Executable file
@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'bundler/setup'
|
||||
require 'faraday'
|
||||
|
||||
# You can add fixtures and/or initialization code here to make experimenting
|
||||
# with your gem easier. You can also use a different console, if you like.
|
||||
|
||||
# (If you use this, don't forget to add pry to your Gemfile!)
|
||||
# require "pry"
|
||||
# Pry.start
|
||||
|
||||
require 'irb'
|
||||
IRB.start(__FILE__)
|
7
bin/setup
Executable file
@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
IFS=$'\n\t'
|
||||
set -vx
|
||||
|
||||
gem install bundler
|
||||
bundle install --jobs 4
|
7
bin/test
Executable file
@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
IFS=$'\n\t'
|
||||
set -vx
|
||||
|
||||
bundle exec rubocop -a --format progress
|
||||
bundle exec rspec
|
3
config/external.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
faraday-net_http:
|
||||
url: https://github.com/lostisland/faraday-net_http.git
|
||||
command: bundle exec rspec
|
0
docs/.nojekyll
Normal file
21
docs/README.md
Normal file
@ -0,0 +1,21 @@
|
||||
# Faraday Docs
|
||||
|
||||
Faraday Docs are powered by [Docsify](https://docsify.js.org/#/).
|
||||
|
||||
## Development
|
||||
|
||||
### Setup
|
||||
|
||||
From the Faraday project root, run the following:
|
||||
|
||||
```bash
|
||||
npm install # this will install the necessary dependencies
|
||||
```
|
||||
|
||||
### Running the Docs Locally
|
||||
|
||||
To preview your changes locally, run the following:
|
||||
|
||||
```bash
|
||||
npm run docs
|
||||
```
|
BIN
docs/_media/favicon.png
Normal file
After Width: | Height: | Size: 700 B |
34
docs/_media/home-logo.svg
Normal file
@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="1240px" height="300px" viewBox="0 0 1240 300" 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>Custom Preset</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs>
|
||||
<linearGradient x1="0%" y1="50.0025809%" x2="113.816993%" y2="50.0025809%" 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>
|
||||
<linearGradient x1="0%" y1="50%" x2="183.555899%" y2="50%" id="linearGradient-2">
|
||||
<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>
|
||||
</defs>
|
||||
<g id="Custom-Preset" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Group" transform="translate(20.000000, 19.000000)">
|
||||
<path d="M345.711907,185.953203 L345.711907,130.649668 L409.242825,130.649668 L409.242825,149.259747 L359.313152,149.259747 L359.313152,185.953203 L345.711907,185.953203 Z M345.711907,81.4653477 L345.711907,78.0438675 L409.242825,78.0438675 L409.242825,96.9280012 L359.313152,96.9280012 L345.711907,81.4653477 Z M475.145406,167.86656 L466.738303,185.953203 L448.442753,185.953203 L497.884985,78.0438675 L530.897775,150.066099 L512.352129,150.066099 L497.884985,118.376317 L483.302413,150.066099 L475.145406,167.86656 Z M547.118435,185.953203 L528.384219,185.953203 L520.084002,167.069069 L538.700762,167.069069 L547.118435,185.953203 Z M622.335524,147.242759 L641.738952,185.953203 L623.642586,185.953203 L596.194284,131.865422 L610.277417,131.998972 C614.530023,132.025452 618.575201,130.097023 621.323011,126.733283 C624.471929,123.282595 626.173163,118.679375 626.054208,113.931554 C626.151915,109.261236 624.44918,104.743216 621.323011,101.377847 C618.575201,98.0141076 614.530023,96.0856782 610.277417,96.1121585 L578.687017,96.1121585 L576.846084,94.0898288 L568.745982,78.0438675 L610.277417,78.0438675 C618.81961,77.9969308 626.950801,81.8406095 632.51588,88.5570399 C638.594115,95.489278 641.88686,104.566395 641.720542,113.931554 C641.851711,121.132427 639.930635,128.213452 636.197745,134.288402 C632.956055,139.967305 628.120172,144.486477 622.335524,147.242759 Z M721.15875,167.86656 L712.751646,185.953203 L694.456097,185.953203 L743.898329,78.0438675 L776.911118,150.066099 L758.365472,150.066099 L743.898329,118.376317 L729.315756,150.066099 L721.15875,167.86656 Z M791.780057,185.953203 L773.04584,185.953203 L764.745623,167.069069 L783.362384,167.069069 L791.780057,185.953203 Z M920.187937,132.046034 C920.259924,139.158187 918.908812,146.217512 916.207905,152.841002 C913.588118,159.130051 909.866145,164.94257 905.203116,170.026926 C900.598652,174.890932 895.040024,178.836169 888.845184,181.636972 C882.637851,184.502668 875.833361,185.978299 868.945023,185.953203 L835.03515,185.953203 L852.049787,167.850042 L869.044524,167.850042 C873.624514,167.867696 878.155982,166.94988 882.337831,165.157581 C886.48057,163.290008 890.178954,160.625132 893.203319,157.328438 C896.305844,153.940452 898.776475,150.064602 900.506678,145.871155 C904.056306,136.940326 904.042131,127.065943 900.466878,118.144531 C898.744294,113.921677 896.228329,110.0382 893.044118,106.687249 C889.978019,103.434133 886.289393,100.776289 882.17863,98.8581057 C878.034816,97.0756201 873.545852,96.1516664 869.004724,96.1465488 L852.009987,96.1465488 L835.03515,78.0438675 L869.044524,78.0438675 C875.931423,78.0308282 882.733032,79.5058345 888.944685,82.3596188 C895.081854,85.1969559 900.594424,89.1382728 905.183216,93.9696651 C909.934465,99.0482971 913.701736,104.900743 916.307405,111.251066 C918.974333,117.880965 920.291628,124.940081 920.187937,132.046034 Z M986.096196,167.86656 L977.689093,185.953203 L959.393543,185.953203 L1008.83578,78.0438675 L1041.84857,150.066099 L1023.30292,150.066099 L1008.83578,118.376317 L994.253203,150.066099 L986.096196,167.86656 Z M1058.06923,185.953203 L1039.3621,185.953203 L1031.03479,167.069069 L1049.62463,167.069069 L1058.06923,185.953203 Z M1150.72976,146.72732 L1150.72976,146.593769 L1122.56982,185.953203 L1101.32432,185.953203 L1140.28084,131.998535 L1101.34363,78.0438675 L1122.58913,78.0438675 L1150.74907,117.42238 L1179.0249,78.0438675 L1200,78.0438675 L1161.37182,131.998535 L1150.72976,146.72732 Z M1149.9863,146.987685 L1121.60014,185.953203 L1149.9863,146.836069 L1149.9863,146.987685 Z" id="Shape" fill="url(#linearGradient-1)" fill-rule="nonzero"></path>
|
||||
<path d="M126.085885,259.671354 L62.1555891,196.228083 L129.023404,129.824126 L195.924855,196.228083 L132.016984,259.671354 C131.230558,260.452312 130.163785,260.89107 129.051434,260.89107 C127.939083,260.89107 126.87231,260.452312 126.085885,259.671354 Z M1.18617949,134.865638 C0.416659626,134.073078 -0.00984556455,133.003843 0.000172577645,131.893267 C0.0109185487,130.782692 0.457336942,129.721798 1.24153626,128.944087 L127.510327,1.33410005 C128.285004,0.549370018 129.33657,0.108388479 130.433165,0.108388479 C131.529759,0.108388479 132.581326,0.549370018 133.356002,1.33410005 L259.569436,128.944087 C260.346251,129.729247 260.782681,130.794302 260.782681,131.904862 C260.782681,133.015423 260.346251,134.080478 259.569436,134.865638 L211.11012,183.861156 L130.388879,102.246734 L49.6565668,183.87235 L1.18617949,134.865638 L1.18617949,134.865638 Z" id="Shape" fill="url(#linearGradient-2)"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 6.1 KiB |
BIN
docs/_media/logo.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
docs/_media/middleware.png
Normal file
After Width: | Height: | Size: 225 KiB |
BIN
docs/_media/overview.png
Normal file
After Width: | Height: | Size: 225 KiB |
BIN
docs/_media/repo-card-slim.png
Normal file
After Width: | Height: | Size: 186 KiB |
BIN
docs/_media/repo-card.png
Normal file
After Width: | Height: | Size: 358 KiB |
33
docs/_sidebar.md
Normal 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)
|
161
docs/adapters/custom/index.md
Normal 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
|
75
docs/adapters/custom/parallel-requests.md
Normal 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).
|
79
docs/adapters/custom/streaming.md
Normal 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
|
60
docs/adapters/custom/testing.md
Normal 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
|
46
docs/adapters/index.md
Normal file
@ -0,0 +1,46 @@
|
||||
# Adapters
|
||||
|
||||
The Faraday Adapter interface determines how a Faraday request is turned into
|
||||
a Faraday response object. Adapters are typically implemented with common Ruby
|
||||
HTTP clients, but can have custom implementations. Adapters can be configured
|
||||
either globally or per Faraday Connection through the configuration block.
|
||||
|
||||
For example, consider using `httpclient` as an adapter. Note that [faraday-httpclient](https://github.com/lostisland/faraday-httpclient) must be installed beforehand.
|
||||
|
||||
If you want to configure it globally, do the following:
|
||||
|
||||
```ruby
|
||||
require 'faraday'
|
||||
require 'faraday/httpclient'
|
||||
|
||||
Faraday.default_adapter = :httpclient
|
||||
```
|
||||
|
||||
If you want to configure it per Faraday Connection, do the following:
|
||||
|
||||
```ruby
|
||||
require 'faraday'
|
||||
require 'faraday/httpclient'
|
||||
|
||||
conn = Faraday.new do |f|
|
||||
f.adapter :httpclient
|
||||
end
|
||||
```
|
||||
|
||||
## Fantastic adapters and where to find them
|
||||
|
||||
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
|
||||
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]!
|
||||
|
||||
|
||||
[testing]: /adapters/test-adapter.md
|
||||
[net_http]: /adapters/net-http.md
|
||||
[awesome]: https://github.com/lostisland/awesome-faraday/#adapters
|
||||
[build_adapters]: /adapters/custom/index.md
|
12
docs/adapters/net-http.md
Normal 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
|
117
docs/adapters/test-adapter.md
Normal file
@ -0,0 +1,117 @@
|
||||
# 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.
|
||||
|
||||
The easiest way to do this is to create the stubbed requests when initializing
|
||||
a `Faraday::Connection`. Stubbing a request by path yields a block with a
|
||||
`Faraday::Env` object. The stub block expects an Array return value with three
|
||||
values: an Integer HTTP status code, a Hash of key/value headers, and a
|
||||
response body.
|
||||
|
||||
```ruby
|
||||
conn = Faraday.new do |builder|
|
||||
builder.adapter :test do |stub|
|
||||
# block returns an array with 3 items:
|
||||
# - Integer response status
|
||||
# - Hash HTTP headers
|
||||
# - String response body
|
||||
stub.get('/ebi') do |env|
|
||||
[
|
||||
200,
|
||||
{ 'Content-Type': 'text/plain', },
|
||||
'shrimp'
|
||||
]
|
||||
end
|
||||
|
||||
# test exceptions too
|
||||
stub.get('/boom') do
|
||||
raise Faraday::ConnectionFailed
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
You can define the stubbed requests outside of the test adapter block:
|
||||
|
||||
```ruby
|
||||
stubs = Faraday::Adapter::Test::Stubs.new do |stub|
|
||||
stub.get('/tamago') { |env| [200, {}, 'egg'] }
|
||||
end
|
||||
```
|
||||
|
||||
This Stubs instance can be passed to a new Connection:
|
||||
|
||||
```ruby
|
||||
conn = Faraday.new do |builder|
|
||||
builder.adapter :test, stubs do |stub|
|
||||
stub.get('/ebi') { |env| [ 200, {}, 'shrimp' ]}
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
It's also possible to stub additional requests after the connection has been
|
||||
initialized. This is useful for testing.
|
||||
|
||||
```ruby
|
||||
stubs.get('/uni') { |env| [ 200, {}, 'urchin' ]}
|
||||
```
|
||||
|
||||
You can also stub the request body with a string or a proc.
|
||||
It would be useful to pass a proc if it's OK only to check the parts of the request body are passed.
|
||||
|
||||
```ruby
|
||||
stubs.post('/kohada', 'where=sea&temperature=24') { |env| [ 200, {}, 'spotted gizzard shad' ]}
|
||||
stubs.post('/anago', -> (request_body) { JSON.parse(request_body).slice('name') == { 'name' => 'Wakamoto' } }) { |env| [200, {}, 'conger eel'] }
|
||||
```
|
||||
|
||||
If you want to stub requests that exactly match a path, parameters, and headers,
|
||||
`strict_mode` would be useful.
|
||||
|
||||
```ruby
|
||||
stubs = Faraday::Adapter::Test::Stubs.new(strict_mode: true) do |stub|
|
||||
stub.get('/ikura?nori=true', 'X-Soy-Sauce' => '5ml' ) { |env| [200, {}, 'ikura gunkan maki'] }
|
||||
end
|
||||
```
|
||||
|
||||
This stub expects the connection will be called like this:
|
||||
|
||||
```ruby
|
||||
conn.get('/ikura', { nori: 'true' }, { 'X-Soy-Sauce' => '5ml' } )
|
||||
```
|
||||
|
||||
If there are other parameters or headers included, the Faraday Test adapter
|
||||
will raise `Faraday::Test::Stubs::NotFound`. It also raises the error
|
||||
if the specified parameters (`nori`) or headers (`X-Soy-Sauce`) are omitted.
|
||||
|
||||
You can also enable `strict_mode` after initializing the connection.
|
||||
In this case, all requests, including ones that have been already stubbed,
|
||||
will be handled in a strict way.
|
||||
|
||||
```ruby
|
||||
stubs.strict_mode = true
|
||||
```
|
||||
|
||||
Finally, you can treat your stubs as mocks by verifying that all of the stubbed
|
||||
calls were made. NOTE: this feature is still fairly experimental. It will not
|
||||
verify the order or count of any stub.
|
||||
|
||||
```ruby
|
||||
stubs.verify_stubbed_calls
|
||||
```
|
||||
|
||||
After the test case is completed (possibly in an `after` hook), you should clear
|
||||
the default connection to prevent it from being cached between different tests.
|
||||
This allows for each test to have its own set of stubs
|
||||
|
||||
```ruby
|
||||
Faraday.default_connection = nil
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
Working [RSpec] and [test/unit] examples for a fictional JSON API client are
|
||||
available.
|
||||
|
||||
[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
|
58
docs/advanced/parallel-requests.md
Normal 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
|
35
docs/advanced/streaming-responses.md
Normal file
@ -0,0 +1,35 @@
|
||||
# Streaming Responses
|
||||
|
||||
Sometimes you might need to receive a streaming response.
|
||||
You can do this with the `on_data` request option.
|
||||
|
||||
The `on_data` callback is a receives tuples of chunk Strings, and the total
|
||||
of received bytes so far.
|
||||
|
||||
This example implements such a callback:
|
||||
|
||||
```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.new do |chunk, overall_received_bytes, env|
|
||||
puts "Received #{overall_received_bytes} characters"
|
||||
streamed << chunk
|
||||
end
|
||||
end
|
||||
|
||||
# Joins all response chunks together
|
||||
streamed.join
|
||||
```
|
||||
|
||||
The `on_data` streaming is currently only supported by some adapters.
|
||||
To see which ones, please refer to [Awesome Faraday][awesome] comparative table or check the adapter documentation.
|
||||
Moreover, the `env` parameter was only recently added, which means some adapters may only have partial support
|
||||
(i.e. only `chunk` and `overall_received_bytes` will be passed to your block).
|
||||
|
||||
[awesome]: https://github.com/lostisland/awesome-faraday/#adapters
|
48
docs/customization/connection-options.md
Normal 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
|
105
docs/customization/index.md
Normal file
@ -0,0 +1,105 @@
|
||||
# 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:
|
||||
|
||||
```ruby
|
||||
conn = Faraday.new('http://httpbingo.org', request: { timeout: 5 })
|
||||
conn.get('/ip')
|
||||
```
|
||||
|
||||
Or as per-request options:
|
||||
|
||||
```ruby
|
||||
conn.get do |req|
|
||||
req.url '/ip'
|
||||
req.options.timeout = 5
|
||||
end
|
||||
```
|
||||
|
||||
You can also inject arbitrary data into the request using the `context` option.
|
||||
This will be available in the `env` on all middleware.
|
||||
|
||||
```ruby
|
||||
conn.get do |req|
|
||||
req.url '/get'
|
||||
req.options.context = {
|
||||
foo: 'foo',
|
||||
bar: 'bar'
|
||||
}
|
||||
end
|
||||
```
|
||||
|
||||
## Changing how parameters are serialized
|
||||
|
||||
Sometimes you need to send the same URL parameter multiple times with different values.
|
||||
This requires manually setting the parameter encoder and can be done on
|
||||
either per-connection or per-request basis.
|
||||
This applies to all HTTP verbs.
|
||||
|
||||
Per-connection setting:
|
||||
|
||||
```ruby
|
||||
conn = Faraday.new request: { params_encoder: Faraday::FlatParamsEncoder }
|
||||
conn.get('', { roll: ['california', 'philadelphia'] })
|
||||
```
|
||||
|
||||
Per-request setting:
|
||||
|
||||
```ruby
|
||||
conn.get do |req|
|
||||
req.options.params_encoder = Faraday::FlatParamsEncoder
|
||||
req.params = { roll: ['california', 'philadelphia'] }
|
||||
end
|
||||
```
|
||||
|
||||
### Custom serializers
|
||||
|
||||
You can build your custom encoder, if you like.
|
||||
|
||||
The value of Faraday `params_encoder` can be any object that responds to:
|
||||
|
||||
* `#encode(hash) #=> String`
|
||||
* `#decode(string) #=> Hash`
|
||||
|
||||
The encoder will affect both how Faraday processes query strings and how it
|
||||
serializes POST bodies.
|
||||
|
||||
The default encoder is `Faraday::NestedParamsEncoder`.
|
||||
|
||||
### Order of parameters
|
||||
|
||||
By default, parameters are sorted by name while being serialized.
|
||||
Since this is really useful to provide better cache management and most servers don't really care about parameters order, this is the default behaviour.
|
||||
However you might find yourself dealing with a server that requires parameters to be in a specific order.
|
||||
When that happens, you can configure the encoder to skip sorting them.
|
||||
This configuration is supported by both the default `Faraday::NestedParamsEncoder` and `Faraday::FlatParamsEncoder`:
|
||||
|
||||
```ruby
|
||||
Faraday::NestedParamsEncoder.sort_params = false
|
||||
# or
|
||||
Faraday::FlatParamsEncoder.sort_params = false
|
||||
```
|
||||
|
||||
## Proxy
|
||||
|
||||
Faraday will try to automatically infer the proxy settings from your system using [`URI#find_proxy`][ruby-find-proxy].
|
||||
This will retrieve them from environment variables such as http_proxy, ftp_proxy, no_proxy, etc.
|
||||
If for any reason you want to disable this behaviour, you can do so by setting the global variable `ignore_env_proxy`:
|
||||
|
||||
```ruby
|
||||
Faraday.ignore_env_proxy = true
|
||||
```
|
||||
|
||||
You can also specify a custom proxy when initializing the connection:
|
||||
|
||||
```ruby
|
||||
conn = Faraday.new('http://www.example.com', proxy: 'http://proxy.com')
|
||||
```
|
||||
|
||||
[ruby-find-proxy]: https://ruby-doc.org/stdlib-2.6.3/libdoc/uri/rdoc/URI/Generic.html#method-i-find_proxy
|
30
docs/customization/proxy-options.md
Normal 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
|
||||
```
|
39
docs/customization/request-options.md
Normal 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
|
35
docs/customization/ssl-options.md
Normal 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
|
||||
```
|
51
docs/getting-started/env-object.md
Normal 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
|
17
docs/getting-started/errors.md
Normal 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.
|
266
docs/getting-started/quick-start.md
Normal file
@ -0,0 +1,266 @@
|
||||
# Quick Start
|
||||
|
||||
## Installation
|
||||
|
||||
Add this line to your application’s `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
|
225
docs/getting-started/rest-client-migration.md
Normal 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
@ -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>
|
28
docs/index.md
Normal file
@ -0,0 +1,28 @@
|
||||
# 
|
||||
|
||||
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.
|
||||
|
||||
## 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!
|
||||
|
||||
## Who uses Faraday?
|
||||
|
||||
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)
|
84
docs/middleware/custom-middleware.md
Normal 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)
|
||||
```
|
65
docs/middleware/included/authentication.md
Normal file
@ -0,0 +1,65 @@
|
||||
# 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.
|
||||
**Please note the way you use this middleware in Faraday 1.x is different**,
|
||||
examples are available at the bottom of this page.
|
||||
|
||||
```ruby
|
||||
Faraday.new(...) do |conn|
|
||||
conn.request :authorization, 'Bearer', 'authentication-token'
|
||||
end
|
||||
```
|
||||
|
||||
### With a proc
|
||||
|
||||
You can also provide a proc, which will be evaluated on each request:
|
||||
|
||||
```ruby
|
||||
Faraday.new(...) do |conn|
|
||||
conn.request :authorization, 'Bearer', -> { MyAuthStorage.get_auth_token }
|
||||
end
|
||||
```
|
||||
|
||||
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|
|
||||
conn.request :authorization, 'Bearer', ->(env) { MyAuthStorage.get_auth_token(env) }
|
||||
end
|
||||
```
|
||||
|
||||
### Basic Authentication
|
||||
|
||||
The middleware will automatically Base64 encode your Basic username and password:
|
||||
|
||||
```ruby
|
||||
Faraday.new(...) do |conn|
|
||||
conn.request :authorization, :basic, 'username', 'password'
|
||||
end
|
||||
```
|
||||
|
||||
### Faraday 1.x usage
|
||||
|
||||
In Faraday 1.x, the way you use this middleware is slightly different:
|
||||
|
||||
```ruby
|
||||
# Basic Auth request
|
||||
# Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
|
||||
Faraday.new(...) do |conn|
|
||||
conn.request :basic_auth, 'username', 'password'
|
||||
end
|
||||
|
||||
# Token Auth request
|
||||
# `options` are automatically converted into `key=value` format
|
||||
# Authorization: Token authentication-token <options>
|
||||
Faraday.new(...) do |conn|
|
||||
conn.request :token_auth, 'authentication-token', **options
|
||||
end
|
||||
|
||||
# Generic Auth Request
|
||||
# Authorization: Bearer authentication-token
|
||||
Faraday.new(...) do |conn|
|
||||
conn.request :authorization, 'Bearer', 'authentication-token'
|
||||
end
|
||||
```
|
37
docs/middleware/included/index.md
Normal file
@ -0,0 +1,37 @@
|
||||
# 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**.
|
||||
The former usually deal with the request, encoding the parameters or setting headers.
|
||||
The latter instead activate after the request is completed and a response has been received, like
|
||||
parsing the response body, logging useful info or checking the response status.
|
||||
|
||||
### Request Middleware
|
||||
|
||||
**Request middleware** can modify Request details before the Adapter runs. Most
|
||||
middleware set Header values or transform the request body based on the
|
||||
content type.
|
||||
|
||||
* [`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.
|
||||
|
||||
|
||||
### Response Middleware
|
||||
|
||||
**Response middleware** receives the response from the adapter and can modify its details
|
||||
before returning it.
|
||||
|
||||
* [`Json Response`][json-response] parses response body into a hash of key/value pairs.
|
||||
* [`Logger`][logger] logs both the request and the response body and headers.
|
||||
* [`RaiseError`][raise_error] checks the response HTTP code and raises an exception if it is a 4xx or 5xx code.
|
||||
|
||||
|
||||
[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
|
34
docs/middleware/included/instrumentation.md
Normal file
@ -0,0 +1,34 @@
|
||||
# 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.
|
||||
They default to `request.faraday` and `ActiveSupport::Notifications` respectively, but you can provide your own:
|
||||
|
||||
```ruby
|
||||
conn = Faraday.new(...) do |f|
|
||||
f.request :instrumentation, name: 'custom_name', instrumenter: MyInstrumenter
|
||||
...
|
||||
end
|
||||
```
|
||||
|
||||
### Example Usage
|
||||
|
||||
The `Instrumentation` middleware will use `ActiveSupport::Notifications` by default as instrumenter,
|
||||
allowing you to subscribe to the default event name and instrument requests:
|
||||
|
||||
```ruby
|
||||
conn = Faraday.new('http://example.com') do |f|
|
||||
f.request :instrumentation
|
||||
...
|
||||
end
|
||||
|
||||
ActiveSupport::Notifications.subscribe('request.faraday') do |name, starts, ends, _, env|
|
||||
url = env[:url]
|
||||
http_method = env[:method].to_s.upcase
|
||||
duration = ends - starts
|
||||
$stdout.puts '[%s] %s %s (%.3f s)' % [url.host, http_method, url.request_uri, duration]
|
||||
end
|
||||
|
||||
conn.get('/search', { a: 1, b: 2 })
|
||||
#=> [example.com] GET /search?a=1&b=2 (0.529 s)
|
||||
```
|
81
docs/middleware/included/json.md
Normal 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
|
||||
```
|
114
docs/middleware/included/logging.md
Normal file
@ -0,0 +1,114 @@
|
||||
# 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.
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```ruby
|
||||
conn = Faraday.new(url: 'http://httpbingo.org') do |faraday|
|
||||
faraday.response :logger # log requests and responses to $stdout
|
||||
end
|
||||
|
||||
conn.get
|
||||
# => INFO -- request: GET http://httpbingo.org/
|
||||
# => DEBUG -- request: User-Agent: "Faraday v1.0.0"
|
||||
# => INFO -- response: Status 301
|
||||
# => DEBUG -- response: date: "Sun, 19 May 2019 16:05:40 GMT"
|
||||
```
|
||||
|
||||
### Customize the logger
|
||||
|
||||
By default, the `Logger` middleware uses the Ruby `Logger.new($stdout)`.
|
||||
You can customize it to use any logger you want by providing it when you add the middleware to the stack:
|
||||
|
||||
```ruby
|
||||
conn = Faraday.new(url: 'http://httpbingo.org') do |faraday|
|
||||
faraday.response :logger, MyLogger.new($stdout)
|
||||
end
|
||||
```
|
||||
|
||||
### Include and exclude headers/bodies
|
||||
|
||||
By default, the `logger` middleware logs only headers for security reasons, however, you can configure it
|
||||
to log bodies and errors as well, or disable headers logging if you need to.
|
||||
To do so, simply provide a configuration hash when you add the middleware to the stack:
|
||||
|
||||
```ruby
|
||||
conn = Faraday.new(url: 'http://httpbingo.org') do |faraday|
|
||||
faraday.response :logger, nil, { headers: true, bodies: true, errors: true }
|
||||
end
|
||||
```
|
||||
|
||||
You can also configure the `logger` middleware with a little more complex settings
|
||||
like "do not log the request bodies, but log the response bodies".
|
||||
|
||||
```ruby
|
||||
conn = Faraday.new(url: 'http://httpbingo.org') do |faraday|
|
||||
faraday.response :logger, nil, { bodies: { request: false, response: true } }
|
||||
end
|
||||
```
|
||||
|
||||
Please note this only works with the default formatter.
|
||||
|
||||
### Filter sensitive information
|
||||
|
||||
You can filter sensitive information from Faraday logs using a regex matcher:
|
||||
|
||||
```ruby
|
||||
conn = Faraday.new(url: 'http://httpbingo.org') do |faraday|
|
||||
faraday.response :logger do | logger |
|
||||
logger.filter(/(api_key=)([^&]+)/, '\1[REMOVED]')
|
||||
end
|
||||
end
|
||||
|
||||
conn.get('/', api_key: 'secret')
|
||||
# => INFO -- request: GET http://httpbingo.org/?api_key=[REMOVED]
|
||||
# => DEBUG -- request: User-Agent: "Faraday v1.0.0"
|
||||
# => INFO -- response: Status 301
|
||||
# => DEBUG -- response: date: "Sun, 19 May 2019 16:12:36 GMT"
|
||||
```
|
||||
|
||||
### Change log level
|
||||
|
||||
By default, the `logger` middleware logs on the `info` log level. It is possible to configure
|
||||
the severity by providing the `log_level` option:
|
||||
|
||||
```ruby
|
||||
conn = Faraday.new(url: 'http://httpbingo.org') do |faraday|
|
||||
faraday.response :logger, nil, { bodies: true, log_level: :debug }
|
||||
end
|
||||
```
|
||||
|
||||
### Customize the formatter
|
||||
|
||||
You can also provide a custom formatter to control how requests, responses and errors are logged.
|
||||
Any custom formatter MUST implement the `request` and `response` method, with one argument which
|
||||
will be passed being the Faraday environment.
|
||||
Any custom formatter CAN implement the `exception` method,
|
||||
with one argument which will be passed being the exception (StandardError).
|
||||
If you make your formatter inheriting from `Faraday::Logging::Formatter`,
|
||||
then the methods `debug`, `info`, `warn`, `error` and `fatal` are automatically delegated to the logger.
|
||||
|
||||
```ruby
|
||||
class MyFormatter < Faraday::Logging::Formatter
|
||||
def request(env)
|
||||
# Build a custom message using `env`
|
||||
info('Request') { 'Sending Request' }
|
||||
end
|
||||
|
||||
def response(env)
|
||||
# Build a custom message using `env`
|
||||
info('Response') { 'Response Received' }
|
||||
end
|
||||
|
||||
def exception(exc)
|
||||
# Build a custom message using `exc`
|
||||
info('Error') { 'Error Raised' }
|
||||
end
|
||||
end
|
||||
|
||||
conn = Faraday.new(url: 'http://httpbingo.org/api_key=s3cr3t') do |faraday|
|
||||
faraday.response :logger, nil, formatter: MyFormatter
|
||||
end
|
||||
```
|
90
docs/middleware/included/raising-errors.md
Normal 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.
|
31
docs/middleware/included/url-encoding.md
Normal file
@ -0,0 +1,31 @@
|
||||
# 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 in the [Request Options](customization/request-options.md).
|
||||
|
||||
|
||||
### Example Usage
|
||||
|
||||
```ruby
|
||||
conn = Faraday.new(...) do |f|
|
||||
f.request :url_encoded
|
||||
...
|
||||
end
|
||||
|
||||
conn.post('/', { a: 1, b: 2 })
|
||||
# POST with
|
||||
# Content-Type: application/x-www-form-urlencoded
|
||||
# Body: a=1&b=2
|
||||
```
|
||||
|
||||
Complex structures can also be passed
|
||||
|
||||
```ruby
|
||||
conn.post('/', { a: [1, 3], b: { c: 2, d: 4} })
|
||||
# POST with
|
||||
# Content-Type: application/x-www-form-urlencoded
|
||||
# Body: a%5B%5D=1&a%5B%5D=3&b%5Bc%5D=2&b%5Bd%5D=4
|
||||
```
|
||||
|
||||
[customize]: ../usage/customize#changing-how-parameters-are-serialized
|
200
docs/middleware/index.md
Normal file
@ -0,0 +1,200 @@
|
||||
# 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.
|
||||
|
||||
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
|
||||
|
||||
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'
|
||||
|
||||
conn = Faraday.new do |f|
|
||||
f.request :json # encode req bodies as JSON
|
||||
f.response :logger # logs request and responses
|
||||
f.response :json # decode response bodies as JSON
|
||||
f.adapter :net_http # Use the Net::HTTP adapter
|
||||
end
|
||||
response = conn.get("http://httpbingo.org/get")
|
||||
```
|
||||
|
||||
### How it Works
|
||||
|
||||
A `Faraday::Connection` uses a `Faraday::RackBuilder` to assemble a
|
||||
Rack-inspired middleware stack for making HTTP requests. Each middleware runs
|
||||
and passes an Env object around to the next one. After the final middleware has
|
||||
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/index.md), it must
|
||||
therefore be last.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||
Say you have the following:
|
||||
|
||||
```ruby
|
||||
Faraday.new(...) do |conn|
|
||||
conn.request :authorization
|
||||
conn.response :json
|
||||
conn.response :parse_dates
|
||||
end
|
||||
```
|
||||
|
||||
This will result into a middleware stack like this:
|
||||
|
||||
```ruby
|
||||
authorization do
|
||||
# authorization request hook
|
||||
json do
|
||||
# json request hook
|
||||
parse_dates do
|
||||
# parse_dates request hook
|
||||
response = adapter.perform(request)
|
||||
# parse_dates response hook
|
||||
end
|
||||
# json response hook
|
||||
end
|
||||
# authorization response hook
|
||||
end
|
||||
```
|
||||
|
||||
In this example, you can see that `parse_dates` is the LAST middleware processing the request, and the FIRST middleware processing the response.
|
||||
This is why it's important for the adapter to always be at the end of the middleware list.
|
||||
|
||||
### Using Middleware
|
||||
|
||||
Calling `use` is the most basic way to add middleware to your stack, but most
|
||||
middleware is conveniently registered in the `request`, `response` or `adapter`
|
||||
namespaces. All four methods are equivalent apart from the namespacing.
|
||||
|
||||
For example, the `Faraday::Request::UrlEncoded` middleware registers itself in
|
||||
`Faraday::Request` so it can be added with `request`. These two are equivalent:
|
||||
|
||||
```ruby
|
||||
# add by symbol, lookup from Faraday::Request,
|
||||
# Faraday::Response and Faraday::Adapter registries
|
||||
conn = Faraday.new do |f|
|
||||
f.request :url_encoded
|
||||
f.response :logger
|
||||
f.adapter :net_http
|
||||
end
|
||||
```
|
||||
|
||||
or:
|
||||
|
||||
```ruby
|
||||
# identical, but add the class directly instead of using lookups
|
||||
conn = Faraday.new do |f|
|
||||
f.use Faraday::Request::UrlEncoded
|
||||
f.use Faraday::Response::Logger
|
||||
f.use Faraday::Adapter::NetHttp
|
||||
end
|
||||
```
|
||||
|
||||
This is also the place to pass options. For example:
|
||||
|
||||
```ruby
|
||||
conn = Faraday.new do |f|
|
||||
f.request :logger, bodies: true
|
||||
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.
|
||||
|
||||
### Detailed Example
|
||||
|
||||
Here's a more realistic example:
|
||||
|
||||
```ruby
|
||||
Faraday.new(...) do |conn|
|
||||
# POST/PUT params encoder
|
||||
conn.request :url_encoded
|
||||
|
||||
# Logging of requests/responses
|
||||
conn.response :logger
|
||||
|
||||
# Last middleware must be the adapter
|
||||
conn.adapter :net_http
|
||||
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.
|
||||
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
|
||||
"Content-Type" for the request is explicitly stating which middleware should
|
||||
process it.
|
119
examples/client_spec.rb
Normal file
@ -0,0 +1,119 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Requires Ruby with rspec and faraday gems.
|
||||
# rspec client_spec.rb
|
||||
|
||||
require 'faraday'
|
||||
require 'json'
|
||||
|
||||
# Example API client
|
||||
class Client
|
||||
def initialize(conn)
|
||||
@conn = conn
|
||||
end
|
||||
|
||||
def httpbingo(jname, params: {})
|
||||
res = @conn.get("/#{jname}", params)
|
||||
data = JSON.parse(res.body)
|
||||
data['origin']
|
||||
end
|
||||
|
||||
def foo(params)
|
||||
res = @conn.post('/foo', JSON.dump(params))
|
||||
res.status
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.describe Client do
|
||||
let(:stubs) { Faraday::Adapter::Test::Stubs.new }
|
||||
let(:conn) { Faraday.new { |b| b.adapter(:test, stubs) } }
|
||||
let(:client) { Client.new(conn) }
|
||||
|
||||
it 'parses origin' do
|
||||
stubs.get('/ip') do |env|
|
||||
# optional: you can inspect the Faraday::Env
|
||||
expect(env.url.path).to eq('/ip')
|
||||
[
|
||||
200,
|
||||
{ 'Content-Type': 'application/javascript' },
|
||||
'{"origin": "127.0.0.1"}'
|
||||
]
|
||||
end
|
||||
|
||||
# uncomment to trigger stubs.verify_stubbed_calls failure
|
||||
# stubs.get('/unused') { [404, {}, ''] }
|
||||
|
||||
expect(client.httpbingo('ip')).to eq('127.0.0.1')
|
||||
stubs.verify_stubbed_calls
|
||||
end
|
||||
|
||||
it 'handles 404' do
|
||||
stubs.get('/api') do
|
||||
[
|
||||
404,
|
||||
{ 'Content-Type': 'application/javascript' },
|
||||
'{}'
|
||||
]
|
||||
end
|
||||
expect(client.httpbingo('api')).to be_nil
|
||||
stubs.verify_stubbed_calls
|
||||
end
|
||||
|
||||
it 'handles exception' do
|
||||
stubs.get('/api') do
|
||||
raise Faraday::ConnectionFailed
|
||||
end
|
||||
|
||||
expect { client.httpbingo('api') }.to raise_error(Faraday::ConnectionFailed)
|
||||
stubs.verify_stubbed_calls
|
||||
end
|
||||
|
||||
context 'When the test stub is run in strict_mode' do
|
||||
let(:stubs) { Faraday::Adapter::Test::Stubs.new(strict_mode: true) }
|
||||
|
||||
it 'verifies the all parameter values are identical' do
|
||||
stubs.get('/api?abc=123') do
|
||||
[
|
||||
200,
|
||||
{ 'Content-Type': 'application/javascript' },
|
||||
'{"origin": "127.0.0.1"}'
|
||||
]
|
||||
end
|
||||
|
||||
# uncomment to raise Stubs::NotFound
|
||||
# expect(client.httpbingo('api', params: { abc: 123, foo: 'Kappa' })).to eq('127.0.0.1')
|
||||
expect(client.httpbingo('api', params: { abc: 123 })).to eq('127.0.0.1')
|
||||
stubs.verify_stubbed_calls
|
||||
end
|
||||
end
|
||||
|
||||
context 'When the Faraday connection is configured with FlatParamsEncoder' do
|
||||
let(:conn) { Faraday.new(request: { params_encoder: Faraday::FlatParamsEncoder }) { |b| b.adapter(:test, stubs) } }
|
||||
|
||||
it 'handles the same multiple URL parameters' do
|
||||
stubs.get('/api?a=x&a=y&a=z') { [200, { 'Content-Type' => 'application/json' }, '{"origin": "127.0.0.1"}'] }
|
||||
|
||||
# uncomment to raise Stubs::NotFound
|
||||
# expect(client.httpbingo('api', params: { a: %w[x y] })).to eq('127.0.0.1')
|
||||
expect(client.httpbingo('api', params: { a: %w[x y z] })).to eq('127.0.0.1')
|
||||
stubs.verify_stubbed_calls
|
||||
end
|
||||
end
|
||||
|
||||
context 'When you want to test the body, you can use a proc as well as string' do
|
||||
it 'tests with a string' do
|
||||
stubs.post('/foo', '{"name":"YK"}') { [200, {}, ''] }
|
||||
|
||||
expect(client.foo(name: 'YK')).to eq 200
|
||||
stubs.verify_stubbed_calls
|
||||
end
|
||||
|
||||
it 'tests with a proc' do
|
||||
check = ->(request_body) { JSON.parse(request_body).slice('name') == { 'name' => 'YK' } }
|
||||
stubs.post('/foo', check) { [200, {}, ''] }
|
||||
|
||||
expect(client.foo(name: 'YK', created_at: Time.now)).to eq 200
|
||||
stubs.verify_stubbed_calls
|
||||
end
|
||||
end
|
||||
end
|
144
examples/client_test.rb
Normal file
@ -0,0 +1,144 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Requires Ruby with test-unit and faraday gems.
|
||||
# ruby client_test.rb
|
||||
|
||||
require 'faraday'
|
||||
require 'json'
|
||||
require 'test/unit'
|
||||
|
||||
# Example API client
|
||||
class Client
|
||||
def initialize(conn)
|
||||
@conn = conn
|
||||
end
|
||||
|
||||
def httpbingo(jname, params: {})
|
||||
res = @conn.get("/#{jname}", params)
|
||||
data = JSON.parse(res.body)
|
||||
data['origin']
|
||||
end
|
||||
|
||||
def foo(params)
|
||||
res = @conn.post('/foo', JSON.dump(params))
|
||||
res.status
|
||||
end
|
||||
end
|
||||
|
||||
# Example API client test
|
||||
class ClientTest < Test::Unit::TestCase
|
||||
def test_httpbingo_name
|
||||
stubs = Faraday::Adapter::Test::Stubs.new
|
||||
stubs.get('/api') do |env|
|
||||
# optional: you can inspect the Faraday::Env
|
||||
assert_equal '/api', env.url.path
|
||||
[
|
||||
200,
|
||||
{ 'Content-Type': 'application/javascript' },
|
||||
'{"origin": "127.0.0.1"}'
|
||||
]
|
||||
end
|
||||
|
||||
# uncomment to trigger stubs.verify_stubbed_calls failure
|
||||
# stubs.get('/unused') { [404, {}, ''] }
|
||||
|
||||
cli = client(stubs)
|
||||
assert_equal '127.0.0.1', cli.httpbingo('api')
|
||||
stubs.verify_stubbed_calls
|
||||
end
|
||||
|
||||
def test_httpbingo_not_found
|
||||
stubs = Faraday::Adapter::Test::Stubs.new
|
||||
stubs.get('/api') do
|
||||
[
|
||||
404,
|
||||
{ 'Content-Type': 'application/javascript' },
|
||||
'{}'
|
||||
]
|
||||
end
|
||||
|
||||
cli = client(stubs)
|
||||
assert_nil cli.httpbingo('api')
|
||||
stubs.verify_stubbed_calls
|
||||
end
|
||||
|
||||
def test_httpbingo_exception
|
||||
stubs = Faraday::Adapter::Test::Stubs.new
|
||||
stubs.get('/api') do
|
||||
raise Faraday::ConnectionFailed
|
||||
end
|
||||
|
||||
cli = client(stubs)
|
||||
assert_raise Faraday::ConnectionFailed do
|
||||
cli.httpbingo('api')
|
||||
end
|
||||
stubs.verify_stubbed_calls
|
||||
end
|
||||
|
||||
def test_strict_mode
|
||||
stubs = Faraday::Adapter::Test::Stubs.new(strict_mode: true)
|
||||
stubs.get('/api?abc=123') do
|
||||
[
|
||||
200,
|
||||
{ 'Content-Type': 'application/javascript' },
|
||||
'{"origin": "127.0.0.1"}'
|
||||
]
|
||||
end
|
||||
|
||||
cli = client(stubs)
|
||||
assert_equal '127.0.0.1', cli.httpbingo('api', params: { abc: 123 })
|
||||
|
||||
# uncomment to raise Stubs::NotFound
|
||||
# assert_equal '127.0.0.1', cli.httpbingo('api', params: { abc: 123, foo: 'Kappa' })
|
||||
stubs.verify_stubbed_calls
|
||||
end
|
||||
|
||||
def test_non_default_params_encoder
|
||||
stubs = Faraday::Adapter::Test::Stubs.new(strict_mode: true)
|
||||
stubs.get('/api?a=x&a=y&a=z') do
|
||||
[
|
||||
200,
|
||||
{ 'Content-Type': 'application/javascript' },
|
||||
'{"origin": "127.0.0.1"}'
|
||||
]
|
||||
end
|
||||
conn = Faraday.new(request: { params_encoder: Faraday::FlatParamsEncoder }) do |builder|
|
||||
builder.adapter :test, stubs
|
||||
end
|
||||
|
||||
cli = Client.new(conn)
|
||||
assert_equal '127.0.0.1', cli.httpbingo('api', params: { a: %w[x y z] })
|
||||
|
||||
# uncomment to raise Stubs::NotFound
|
||||
# assert_equal '127.0.0.1', cli.httpbingo('api', params: { a: %w[x y] })
|
||||
stubs.verify_stubbed_calls
|
||||
end
|
||||
|
||||
def test_with_string_body
|
||||
stubs = Faraday::Adapter::Test::Stubs.new do |stub|
|
||||
stub.post('/foo', '{"name":"YK"}') { [200, {}, ''] }
|
||||
end
|
||||
cli = client(stubs)
|
||||
assert_equal 200, cli.foo(name: 'YK')
|
||||
|
||||
stubs.verify_stubbed_calls
|
||||
end
|
||||
|
||||
def test_with_proc_body
|
||||
stubs = Faraday::Adapter::Test::Stubs.new do |stub|
|
||||
check = ->(request_body) { JSON.parse(request_body).slice('name') == { 'name' => 'YK' } }
|
||||
stub.post('/foo', check) { [200, {}, ''] }
|
||||
end
|
||||
cli = client(stubs)
|
||||
assert_equal 200, cli.foo(name: 'YK', created_at: Time.now)
|
||||
|
||||
stubs.verify_stubbed_calls
|
||||
end
|
||||
|
||||
def client(stubs)
|
||||
conn = Faraday.new do |builder|
|
||||
builder.adapter :test, stubs
|
||||
end
|
||||
Client.new(conn)
|
||||
end
|
||||
end
|
@ -1,22 +1,39 @@
|
||||
lib = "faraday"
|
||||
lib_file = File.expand_path("../lib/#{lib}.rb", __FILE__)
|
||||
File.read(lib_file) =~ /\bVERSION\s*=\s*["'](.+?)["']/
|
||||
version = $1
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'lib/faraday/version'
|
||||
|
||||
Gem::Specification.new do |spec|
|
||||
spec.name = lib
|
||||
spec.version = version
|
||||
spec.name = 'faraday'
|
||||
spec.version = Faraday::VERSION
|
||||
|
||||
spec.summary = "HTTP/REST API client library."
|
||||
spec.summary = 'HTTP/REST API client library.'
|
||||
|
||||
spec.authors = ["Rick Olson"]
|
||||
spec.authors = ['@technoweenie', '@iMacTia', '@olleolleolle']
|
||||
spec.email = 'technoweenie@gmail.com'
|
||||
spec.homepage = 'https://github.com/lostisland/faraday'
|
||||
spec.homepage = 'https://lostisland.github.io/faraday'
|
||||
spec.licenses = ['MIT']
|
||||
|
||||
spec.required_ruby_version = '>= 1.9'
|
||||
spec.required_ruby_version = '>= 3.0'
|
||||
|
||||
spec.add_dependency 'multipart-post', '>= 1.2', '< 3'
|
||||
# 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.
|
||||
# To avoid releasing a major version every time there's a new Faraday API, we should
|
||||
# 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.5'
|
||||
spec.add_dependency 'json'
|
||||
spec.add_dependency 'logger'
|
||||
|
||||
spec.files = `git ls-files -z lib LICENSE.md README.md`.split("\0")
|
||||
# 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']
|
||||
spec.require_paths = %w[lib spec/external_adapters]
|
||||
spec.metadata = {
|
||||
'homepage_uri' => 'https://lostisland.github.io/faraday',
|
||||
'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',
|
||||
'rubygems_mfa_required' => 'true'
|
||||
}
|
||||
end
|
||||
|
314
lib/faraday.rb
@ -1,248 +1,158 @@
|
||||
require 'thread'
|
||||
require 'cgi'
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'cgi/escape'
|
||||
require 'cgi/util' if RUBY_VERSION < '3.5'
|
||||
require 'date'
|
||||
require 'set'
|
||||
require 'forwardable'
|
||||
|
||||
# Public: This is the main namespace for Faraday. You can either use it to
|
||||
# create Faraday::Connection objects, or access it directly.
|
||||
require 'faraday/version'
|
||||
require 'faraday/methods'
|
||||
require 'faraday/error'
|
||||
require 'faraday/middleware_registry'
|
||||
require 'faraday/utils'
|
||||
require 'faraday/options'
|
||||
require 'faraday/connection'
|
||||
require 'faraday/rack_builder'
|
||||
require 'faraday/parameters'
|
||||
require 'faraday/middleware'
|
||||
require 'faraday/adapter'
|
||||
require 'faraday/request'
|
||||
require 'faraday/response'
|
||||
require 'faraday/net_http'
|
||||
# This is the main namespace for Faraday.
|
||||
#
|
||||
# Examples
|
||||
# It provides methods to create {Connection} objects, and HTTP-related
|
||||
# methods to use directly.
|
||||
#
|
||||
# @example Helpful class methods for easy usage
|
||||
# Faraday.get "http://faraday.com"
|
||||
#
|
||||
# @example Helpful class method `.new` to create {Connection} objects.
|
||||
# conn = Faraday.new "http://faraday.com"
|
||||
# conn.get '/'
|
||||
#
|
||||
module Faraday
|
||||
VERSION = "0.15.3"
|
||||
CONTENT_TYPE = 'Content-Type'
|
||||
|
||||
class << self
|
||||
# Public: Gets or sets the root path that Faraday is being loaded from.
|
||||
# This is the root from where the libraries are auto-loaded from.
|
||||
# The root path that Faraday is being loaded from.
|
||||
#
|
||||
# This is the root from where the libraries are auto-loaded.
|
||||
#
|
||||
# @return [String]
|
||||
attr_accessor :root_path
|
||||
|
||||
# Public: Gets or sets the path that the Faraday libs are loaded from.
|
||||
# Gets or sets the path that the Faraday libs are loaded from.
|
||||
# @return [String]
|
||||
attr_accessor :lib_path
|
||||
|
||||
# Public: Gets or sets the Symbol key identifying a default Adapter to use
|
||||
# for the default Faraday::Connection.
|
||||
# @overload default_adapter
|
||||
# Gets the Symbol key identifying a default Adapter to use
|
||||
# for the default {Faraday::Connection}. Defaults to `:net_http`.
|
||||
# @return [Symbol] the default adapter
|
||||
# @overload default_adapter=(adapter)
|
||||
# Updates default adapter while resetting {.default_connection}.
|
||||
# @return [Symbol] the new default_adapter.
|
||||
attr_reader :default_adapter
|
||||
|
||||
# Public: Sets the default Faraday::Connection for simple scripts that
|
||||
# access the Faraday constant directly.
|
||||
#
|
||||
# Faraday.get "https://faraday.com"
|
||||
# Option for the default_adapter
|
||||
# @return [Hash] default_adapter options
|
||||
attr_accessor :default_adapter_options
|
||||
|
||||
# Documented below, see default_connection
|
||||
attr_writer :default_connection
|
||||
|
||||
# Public: Tells faraday to ignore the environment proxy (http_proxy).
|
||||
# Tells Faraday to ignore the environment proxy (http_proxy).
|
||||
# Defaults to `false`.
|
||||
# @return [Boolean]
|
||||
attr_accessor :ignore_env_proxy
|
||||
|
||||
# Public: Initializes a new Faraday::Connection.
|
||||
# Initializes a new {Connection}.
|
||||
#
|
||||
# url - The optional String base URL to use as a prefix for all
|
||||
# requests. Can also be the options Hash.
|
||||
# options - The optional Hash used to configure this Faraday::Connection.
|
||||
# Any of these values will be set on every request made, unless
|
||||
# overridden for a specific request.
|
||||
# :url - String base URL.
|
||||
# :params - Hash of URI query unencoded key/value pairs.
|
||||
# :headers - Hash of unencoded HTTP header key/value pairs.
|
||||
# :request - Hash of request options.
|
||||
# :ssl - Hash of SSL options.
|
||||
# :proxy - Hash of Proxy options.
|
||||
#
|
||||
# Examples
|
||||
# @param url [String,Hash] The optional String base URL to use as a prefix
|
||||
# for all requests. Can also be the options Hash. Any of these
|
||||
# values will be set on every request made, unless overridden
|
||||
# for a specific request.
|
||||
# @param options [Hash]
|
||||
# @option options [String] :url Base URL
|
||||
# @option options [Hash] :params Hash of unencoded URI query params.
|
||||
# @option options [Hash] :headers Hash of unencoded HTTP headers.
|
||||
# @option options [Hash] :request Hash of request options.
|
||||
# @option options [Hash] :ssl Hash of SSL options.
|
||||
# @option options [Hash] :proxy Hash of Proxy options.
|
||||
# @return [Faraday::Connection]
|
||||
#
|
||||
# @example With an URL argument
|
||||
# Faraday.new 'http://faraday.com'
|
||||
# # => Faraday::Connection to http://faraday.com
|
||||
#
|
||||
# # http://faraday.com?page=1
|
||||
# Faraday.new 'http://faraday.com', :params => {:page => 1}
|
||||
# @example With an URL argument and an options hash
|
||||
# Faraday.new 'http://faraday.com', params: { page: 1 }
|
||||
# # => Faraday::Connection to http://faraday.com?page=1
|
||||
#
|
||||
# # same
|
||||
#
|
||||
# Faraday.new :url => 'http://faraday.com',
|
||||
# :params => {:page => 1}
|
||||
#
|
||||
# Returns a Faraday::Connection.
|
||||
def new(url = nil, options = nil)
|
||||
block = block_given? ? Proc.new : nil
|
||||
options = options ? default_connection_options.merge(options) : default_connection_options
|
||||
# @example With everything in an options hash
|
||||
# Faraday.new url: 'http://faraday.com',
|
||||
# params: { page: 1 }
|
||||
# # => Faraday::Connection to http://faraday.com?page=1
|
||||
def new(url = nil, options = {}, &block)
|
||||
options = Utils.deep_merge(default_connection_options, options)
|
||||
Faraday::Connection.new(url, options, &block)
|
||||
end
|
||||
|
||||
# Internal: Requires internal Faraday libraries.
|
||||
#
|
||||
# *libs - One or more relative String names to Faraday classes.
|
||||
#
|
||||
# Returns nothing.
|
||||
def require_libs(*libs)
|
||||
libs.each do |lib|
|
||||
require "#{lib_path}/#{lib}"
|
||||
end
|
||||
end
|
||||
|
||||
# Public: Updates default adapter while resetting
|
||||
# #default_connection.
|
||||
#
|
||||
# Returns the new default_adapter.
|
||||
# Documented elsewhere, see default_adapter reader
|
||||
def default_adapter=(adapter)
|
||||
@default_connection = nil
|
||||
@default_adapter = adapter
|
||||
end
|
||||
|
||||
alias require_lib require_libs
|
||||
|
||||
def respond_to?(symbol, include_private = false)
|
||||
def respond_to_missing?(symbol, include_private = false)
|
||||
default_connection.respond_to?(symbol, include_private) || super
|
||||
end
|
||||
|
||||
private
|
||||
# @overload default_connection
|
||||
# Gets the default connection used for simple scripts.
|
||||
# @return [Faraday::Connection] a connection configured with
|
||||
# the default_adapter.
|
||||
# @overload default_connection=(connection)
|
||||
# @param connection [Faraday::Connection]
|
||||
# Sets the default {Faraday::Connection} for simple scripts that
|
||||
# access the Faraday constant directly, such as
|
||||
# <code>Faraday.get "https://faraday.com"</code>.
|
||||
def default_connection
|
||||
@default_connection ||= Connection.new(default_connection_options)
|
||||
end
|
||||
|
||||
# Gets the default connection options used when calling {Faraday#new}.
|
||||
#
|
||||
# @return [Faraday::ConnectionOptions]
|
||||
def default_connection_options
|
||||
@default_connection_options ||= ConnectionOptions.new
|
||||
end
|
||||
|
||||
# Sets the default options used when calling {Faraday#new}.
|
||||
#
|
||||
# @param options [Hash, Faraday::ConnectionOptions]
|
||||
def default_connection_options=(options)
|
||||
@default_connection = nil
|
||||
@default_connection_options = ConnectionOptions.from(options)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Internal: Proxies method calls on the Faraday constant to
|
||||
# #default_connection.
|
||||
# .default_connection.
|
||||
def method_missing(name, *args, &block)
|
||||
default_connection.send(name, *args, &block)
|
||||
if default_connection.respond_to?(name)
|
||||
default_connection.send(name, *args, &block)
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.ignore_env_proxy = false
|
||||
self.root_path = File.expand_path "..", __FILE__
|
||||
self.lib_path = File.expand_path "../faraday", __FILE__
|
||||
self.root_path = File.expand_path __dir__
|
||||
self.lib_path = File.expand_path 'faraday', __dir__
|
||||
self.default_adapter = :net_http
|
||||
|
||||
# Gets the default connection used for simple scripts.
|
||||
#
|
||||
# Returns a Faraday::Connection, configured with the #default_adapter.
|
||||
def self.default_connection
|
||||
@default_connection ||= Connection.new(default_connection_options)
|
||||
end
|
||||
|
||||
# Gets the default connection options used when calling Faraday#new.
|
||||
#
|
||||
# Returns a Faraday::ConnectionOptions.
|
||||
def self.default_connection_options
|
||||
@default_connection_options ||= ConnectionOptions.new
|
||||
end
|
||||
|
||||
# Public: Sets the default options used when calling Faraday#new.
|
||||
def self.default_connection_options=(options)
|
||||
@default_connection = nil
|
||||
@default_connection_options = ConnectionOptions.from(options)
|
||||
end
|
||||
|
||||
unless const_defined? :Timer
|
||||
require 'timeout'
|
||||
Timer = Timeout
|
||||
end
|
||||
|
||||
# Public: Adds the ability for other modules to register and lookup
|
||||
# middleware classes.
|
||||
module MiddlewareRegistry
|
||||
# Public: Register middleware class(es) on the current module.
|
||||
#
|
||||
# mapping - A Hash mapping Symbol keys to classes. Classes can be expressed
|
||||
# as fully qualified constant, or a Proc that will be lazily
|
||||
# called to return the former.
|
||||
#
|
||||
# Examples
|
||||
#
|
||||
# module Faraday
|
||||
# class Whatever
|
||||
# # Middleware looked up by :foo returns Faraday::Whatever::Foo.
|
||||
# register_middleware :foo => Foo
|
||||
#
|
||||
# # Middleware looked up by :bar returns Faraday::Whatever.const_get(:Bar)
|
||||
# register_middleware :bar => :Bar
|
||||
#
|
||||
# # Middleware looked up by :baz requires 'baz' and returns Faraday::Whatever.const_get(:Baz)
|
||||
# register_middleware :baz => [:Baz, 'baz']
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Returns nothing.
|
||||
def register_middleware(autoload_path = nil, mapping = nil)
|
||||
if mapping.nil?
|
||||
mapping = autoload_path
|
||||
autoload_path = nil
|
||||
end
|
||||
middleware_mutex do
|
||||
@middleware_autoload_path = autoload_path if autoload_path
|
||||
(@registered_middleware ||= {}).update(mapping)
|
||||
end
|
||||
end
|
||||
|
||||
# Public: Lookup middleware class with a registered Symbol shortcut.
|
||||
#
|
||||
# key - The Symbol key for the registered middleware.
|
||||
#
|
||||
# Examples
|
||||
#
|
||||
# module Faraday
|
||||
# class Whatever
|
||||
# register_middleware :foo => Foo
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Faraday::Whatever.lookup_middleware(:foo)
|
||||
# # => Faraday::Whatever::Foo
|
||||
#
|
||||
# Returns a middleware Class.
|
||||
def lookup_middleware(key)
|
||||
load_middleware(key) ||
|
||||
raise(Faraday::Error.new("#{key.inspect} is not registered on #{self}"))
|
||||
end
|
||||
|
||||
def middleware_mutex(&block)
|
||||
@middleware_mutex ||= begin
|
||||
require 'monitor'
|
||||
Monitor.new
|
||||
end
|
||||
@middleware_mutex.synchronize(&block)
|
||||
end
|
||||
|
||||
def fetch_middleware(key)
|
||||
defined?(@registered_middleware) && @registered_middleware[key]
|
||||
end
|
||||
|
||||
def load_middleware(key)
|
||||
value = fetch_middleware(key)
|
||||
case value
|
||||
when Module
|
||||
value
|
||||
when Symbol, String
|
||||
middleware_mutex do
|
||||
@registered_middleware[key] = const_get(value)
|
||||
end
|
||||
when Proc
|
||||
middleware_mutex do
|
||||
@registered_middleware[key] = value.call
|
||||
end
|
||||
when Array
|
||||
middleware_mutex do
|
||||
const, path = value
|
||||
if root = @middleware_autoload_path
|
||||
path = "#{root}/#{path}"
|
||||
end
|
||||
require(path)
|
||||
@registered_middleware[key] = const
|
||||
end
|
||||
load_middleware(key)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.const_missing(name)
|
||||
if name.to_sym == :Builder
|
||||
warn "Faraday::Builder is now Faraday::RackBuilder."
|
||||
const_set name, RackBuilder
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
require_libs "utils", "options", "connection", "rack_builder", "parameters",
|
||||
"middleware", "adapter", "request", "response", "upload_io", "error"
|
||||
|
||||
if !ENV["FARADAY_NO_AUTOLOAD"]
|
||||
require_lib 'autoload'
|
||||
end
|
||||
self.default_adapter_options = {}
|
||||
end
|
||||
|
@ -1,55 +1,101 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Faraday
|
||||
# Public: This is a base class for all Faraday adapters. Adapters are
|
||||
# Base class for all Faraday adapters. Adapters are
|
||||
# responsible for fulfilling a Faraday request.
|
||||
class Adapter < Middleware
|
||||
CONTENT_LENGTH = 'Content-Length'.freeze
|
||||
class Adapter
|
||||
extend MiddlewareRegistry
|
||||
|
||||
register_middleware File.expand_path('../adapter', __FILE__),
|
||||
:test => [:Test, 'test'],
|
||||
:net_http => [:NetHttp, 'net_http'],
|
||||
:net_http_persistent => [:NetHttpPersistent, 'net_http_persistent'],
|
||||
:typhoeus => [:Typhoeus, 'typhoeus'],
|
||||
:patron => [:Patron, 'patron'],
|
||||
:em_synchrony => [:EMSynchrony, 'em_synchrony'],
|
||||
:em_http => [:EMHttp, 'em_http'],
|
||||
:excon => [:Excon, 'excon'],
|
||||
:rack => [:Rack, 'rack'],
|
||||
:httpclient => [:HTTPClient, 'httpclient']
|
||||
CONTENT_LENGTH = 'Content-Length'
|
||||
|
||||
# Public: This module marks an Adapter as supporting parallel requests.
|
||||
# This module marks an Adapter as supporting parallel requests.
|
||||
module Parallelism
|
||||
attr_writer :supports_parallel
|
||||
def supports_parallel?() @supports_parallel end
|
||||
|
||||
def supports_parallel?
|
||||
@supports_parallel
|
||||
end
|
||||
|
||||
def inherited(subclass)
|
||||
super
|
||||
subclass.supports_parallel = self.supports_parallel?
|
||||
subclass.supports_parallel = supports_parallel?
|
||||
end
|
||||
end
|
||||
|
||||
extend Parallelism
|
||||
self.supports_parallel = false
|
||||
|
||||
def initialize(app = nil, opts = {}, &block)
|
||||
super(app)
|
||||
def initialize(_app = nil, opts = {}, &block)
|
||||
@app = lambda(&:response)
|
||||
@connection_options = opts
|
||||
@config_block = block
|
||||
end
|
||||
|
||||
# Yields or returns an adapter's configured connection. Depends on
|
||||
# #build_connection being defined on this adapter.
|
||||
#
|
||||
# @param env [Faraday::Env, Hash] The env object for a faraday request.
|
||||
#
|
||||
# @return The return value of the given block, or the HTTP connection object
|
||||
# if no block is given.
|
||||
def connection(env)
|
||||
conn = build_connection(env)
|
||||
return conn unless block_given?
|
||||
|
||||
yield conn
|
||||
end
|
||||
|
||||
# Close any persistent connections. The adapter should still be usable
|
||||
# after calling close.
|
||||
def close
|
||||
# Possible implementation:
|
||||
# @app.close if @app.respond_to?(:close)
|
||||
end
|
||||
|
||||
def call(env)
|
||||
env.clear_body if env.needs_body?
|
||||
env.response = Response.new
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def save_response(env, status, body, headers = nil, reason_phrase = nil)
|
||||
def save_response(env, status, body, headers = nil, reason_phrase = nil, finished: true)
|
||||
env.status = status
|
||||
env.body = body
|
||||
env.reason_phrase = reason_phrase && reason_phrase.to_s.strip
|
||||
env.reason_phrase = reason_phrase&.to_s&.strip
|
||||
env.response_headers = Utils::Headers.new.tap do |response_headers|
|
||||
response_headers.update headers unless headers.nil?
|
||||
yield(response_headers) if block_given?
|
||||
end
|
||||
|
||||
env.response.finish(env) unless env.parallel? || !finished
|
||||
env.response
|
||||
end
|
||||
|
||||
# Fetches either a read, write, or open timeout setting. Defaults to the
|
||||
# :timeout value if a more specific one is not given.
|
||||
#
|
||||
# @param type [Symbol] Describes which timeout setting to get: :read,
|
||||
# :write, or :open.
|
||||
# @param options [Hash] Hash containing Symbol keys like :timeout,
|
||||
# :read_timeout, :write_timeout, or :open_timeout
|
||||
#
|
||||
# @return [Integer, nil] Timeout duration in seconds, or nil if no timeout
|
||||
# has been set.
|
||||
def request_timeout(type, options)
|
||||
key = TIMEOUT_KEYS.fetch(type) do
|
||||
msg = "Expected :read, :write, :open. Got #{type.inspect} :("
|
||||
raise ArgumentError, msg
|
||||
end
|
||||
options[key] || options[:timeout]
|
||||
end
|
||||
|
||||
TIMEOUT_KEYS = {
|
||||
read: :read_timeout,
|
||||
open: :open_timeout,
|
||||
write: :write_timeout
|
||||
}.freeze
|
||||
end
|
||||
end
|
||||
|
||||
require 'faraday/adapter/test'
|
||||
|
@ -1,243 +0,0 @@
|
||||
module Faraday
|
||||
class Adapter
|
||||
# EventMachine adapter is useful for either asynchronous requests
|
||||
# when in EM reactor loop or for making parallel requests in
|
||||
# synchronous code.
|
||||
class EMHttp < Faraday::Adapter
|
||||
module Options
|
||||
def connection_config(env)
|
||||
options = {}
|
||||
configure_proxy(options, env)
|
||||
configure_timeout(options, env)
|
||||
configure_socket(options, env)
|
||||
configure_ssl(options, env)
|
||||
options
|
||||
end
|
||||
|
||||
def request_config(env)
|
||||
options = {
|
||||
:body => read_body(env),
|
||||
:head => env[:request_headers],
|
||||
# :keepalive => true,
|
||||
# :file => 'path/to/file', # stream data off disk
|
||||
}
|
||||
configure_compression(options, env)
|
||||
options
|
||||
end
|
||||
|
||||
def read_body(env)
|
||||
body = env[:body]
|
||||
body.respond_to?(:read) ? body.read : body
|
||||
end
|
||||
|
||||
def configure_proxy(options, env)
|
||||
if proxy = request_options(env)[:proxy]
|
||||
options[:proxy] = {
|
||||
:host => proxy[:uri].host,
|
||||
:port => proxy[:uri].port,
|
||||
:authorization => [proxy[:user], proxy[:password]]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def configure_socket(options, env)
|
||||
if bind = request_options(env)[:bind]
|
||||
options[:bind] = {
|
||||
:host => bind[:host],
|
||||
:port => bind[:port]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def configure_ssl(options, env)
|
||||
if env[:url].scheme == 'https' && env[:ssl]
|
||||
options[:ssl] = {
|
||||
:cert_chain_file => env[:ssl][:ca_file],
|
||||
:verify_peer => env[:ssl].fetch(:verify, true)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def configure_timeout(options, env)
|
||||
timeout, open_timeout = request_options(env).values_at(:timeout, :open_timeout)
|
||||
options[:connect_timeout] = options[:inactivity_timeout] = timeout
|
||||
options[:connect_timeout] = open_timeout if open_timeout
|
||||
end
|
||||
|
||||
def configure_compression(options, env)
|
||||
if env[:method] == :get and not options[:head].key? 'accept-encoding'
|
||||
options[:head]['accept-encoding'] = 'gzip, compressed'
|
||||
end
|
||||
end
|
||||
|
||||
def request_options(env)
|
||||
env[:request]
|
||||
end
|
||||
end
|
||||
|
||||
include Options
|
||||
|
||||
dependency 'em-http'
|
||||
|
||||
self.supports_parallel = true
|
||||
|
||||
def self.setup_parallel_manager(options = nil)
|
||||
Manager.new
|
||||
end
|
||||
|
||||
def call(env)
|
||||
super
|
||||
perform_request env
|
||||
@app.call env
|
||||
end
|
||||
|
||||
def perform_request(env)
|
||||
if parallel?(env)
|
||||
manager = env[:parallel_manager]
|
||||
manager.add {
|
||||
perform_single_request(env).
|
||||
callback { env[:response].finish(env) }
|
||||
}
|
||||
else
|
||||
unless EventMachine.reactor_running?
|
||||
error = nil
|
||||
# start EM, block until request is completed
|
||||
EventMachine.run do
|
||||
perform_single_request(env).
|
||||
callback { EventMachine.stop }.
|
||||
errback { |client|
|
||||
error = error_message(client)
|
||||
EventMachine.stop
|
||||
}
|
||||
end
|
||||
raise_error(error) if error
|
||||
else
|
||||
# EM is running: instruct upstream that this is an async request
|
||||
env[:parallel_manager] = true
|
||||
perform_single_request(env).
|
||||
callback { env[:response].finish(env) }.
|
||||
errback {
|
||||
# TODO: no way to communicate the error in async mode
|
||||
raise NotImplementedError
|
||||
}
|
||||
end
|
||||
end
|
||||
rescue EventMachine::Connectify::CONNECTError => err
|
||||
if err.message.include?("Proxy Authentication Required")
|
||||
raise Error::ConnectionFailed, %{407 "Proxy Authentication Required "}
|
||||
else
|
||||
raise Error::ConnectionFailed, err
|
||||
end
|
||||
rescue => err
|
||||
if defined?(OpenSSL) && OpenSSL::SSL::SSLError === err
|
||||
raise Faraday::SSLError, err
|
||||
else
|
||||
raise
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: reuse the connection to support pipelining
|
||||
def perform_single_request(env)
|
||||
req = create_request(env)
|
||||
req.setup_request(env[:method], request_config(env)).callback { |client|
|
||||
status = client.response_header.status
|
||||
reason = client.response_header.http_reason
|
||||
save_response(env, status, client.response, nil, reason) do |resp_headers|
|
||||
client.response_header.each do |name, value|
|
||||
resp_headers[name.to_sym] = value
|
||||
end
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
def create_request(env)
|
||||
EventMachine::HttpRequest.new(env[:url], connection_config(env).merge(@connection_options))
|
||||
end
|
||||
|
||||
def error_message(client)
|
||||
client.error or "request failed"
|
||||
end
|
||||
|
||||
def raise_error(msg)
|
||||
errklass = Faraday::Error::ClientError
|
||||
if msg == Errno::ETIMEDOUT
|
||||
errklass = Faraday::Error::TimeoutError
|
||||
msg = "request timed out"
|
||||
elsif msg == Errno::ECONNREFUSED
|
||||
errklass = Faraday::Error::ConnectionFailed
|
||||
msg = "connection refused"
|
||||
elsif msg == "connection closed by server"
|
||||
errklass = Faraday::Error::ConnectionFailed
|
||||
end
|
||||
raise errklass, msg
|
||||
end
|
||||
|
||||
def parallel?(env)
|
||||
!!env[:parallel_manager]
|
||||
end
|
||||
|
||||
# The parallel manager is designed to start an EventMachine loop
|
||||
# and block until all registered requests have been completed.
|
||||
class Manager
|
||||
def initialize
|
||||
reset
|
||||
end
|
||||
|
||||
def reset
|
||||
@registered_procs = []
|
||||
@num_registered = 0
|
||||
@num_succeeded = 0
|
||||
@errors = []
|
||||
@running = false
|
||||
end
|
||||
|
||||
def running?() @running end
|
||||
|
||||
def add
|
||||
if running?
|
||||
perform_request { yield }
|
||||
else
|
||||
@registered_procs << Proc.new
|
||||
end
|
||||
@num_registered += 1
|
||||
end
|
||||
|
||||
def run
|
||||
if @num_registered > 0
|
||||
@running = true
|
||||
EventMachine.run do
|
||||
@registered_procs.each do |proc|
|
||||
perform_request(&proc)
|
||||
end
|
||||
end
|
||||
if @errors.size > 0
|
||||
raise Faraday::Error::ClientError, @errors.first || "connection failed"
|
||||
end
|
||||
end
|
||||
ensure
|
||||
reset
|
||||
end
|
||||
|
||||
def perform_request
|
||||
client = yield
|
||||
client.callback { @num_succeeded += 1; check_finished }
|
||||
client.errback { @errors << client.error; check_finished }
|
||||
end
|
||||
|
||||
def check_finished
|
||||
if @num_succeeded + @errors.size == @num_registered
|
||||
EventMachine.stop
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
begin
|
||||
require 'openssl'
|
||||
rescue LoadError
|
||||
warn "Warning: no such file to load -- openssl. Make sure it is installed if you want HTTPS support"
|
||||
else
|
||||
require 'faraday/adapter/em_http_ssl_patch'
|
||||
end if Faraday::Adapter::EMHttp.loaded?
|
@ -1,56 +0,0 @@
|
||||
require 'openssl'
|
||||
require 'em-http'
|
||||
|
||||
module EmHttpSslPatch
|
||||
def ssl_verify_peer(cert_string)
|
||||
cert = nil
|
||||
begin
|
||||
cert = OpenSSL::X509::Certificate.new(cert_string)
|
||||
rescue OpenSSL::X509::CertificateError
|
||||
return false
|
||||
end
|
||||
|
||||
@last_seen_cert = cert
|
||||
|
||||
if certificate_store.verify(@last_seen_cert)
|
||||
begin
|
||||
certificate_store.add_cert(@last_seen_cert)
|
||||
rescue OpenSSL::X509::StoreError => e
|
||||
raise e unless e.message == 'cert already in hash table'
|
||||
end
|
||||
true
|
||||
else
|
||||
raise OpenSSL::SSL::SSLError.new(%(unable to verify the server certificate for "#{host}"))
|
||||
end
|
||||
end
|
||||
|
||||
def ssl_handshake_completed
|
||||
return true unless verify_peer?
|
||||
|
||||
unless OpenSSL::SSL.verify_certificate_identity(@last_seen_cert, host)
|
||||
raise OpenSSL::SSL::SSLError.new(%(host "#{host}" does not match the server certificate))
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def verify_peer?
|
||||
parent.connopts.tls[:verify_peer]
|
||||
end
|
||||
|
||||
def host
|
||||
parent.uri.host
|
||||
end
|
||||
|
||||
def certificate_store
|
||||
@certificate_store ||= begin
|
||||
store = OpenSSL::X509::Store.new
|
||||
store.set_default_paths
|
||||
ca_file = parent.connopts.tls[:cert_chain_file]
|
||||
store.add_file(ca_file) if ca_file
|
||||
store
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
EventMachine::HttpStubConnection.send(:include, EmHttpSslPatch)
|
@ -1,106 +0,0 @@
|
||||
require 'uri'
|
||||
|
||||
module Faraday
|
||||
class Adapter
|
||||
class EMSynchrony < Faraday::Adapter
|
||||
include EMHttp::Options
|
||||
|
||||
dependency do
|
||||
require 'em-synchrony/em-http'
|
||||
require 'em-synchrony/em-multi'
|
||||
require 'fiber'
|
||||
end
|
||||
|
||||
self.supports_parallel = true
|
||||
|
||||
def self.setup_parallel_manager(options = {})
|
||||
ParallelManager.new
|
||||
end
|
||||
|
||||
def call(env)
|
||||
super
|
||||
request = create_request(env)
|
||||
|
||||
http_method = env[:method].to_s.downcase.to_sym
|
||||
|
||||
# Queue requests for parallel execution.
|
||||
if env[:parallel_manager]
|
||||
env[:parallel_manager].add(request, http_method, request_config(env)) do |resp|
|
||||
save_response(env, resp.response_header.status, resp.response) do |resp_headers|
|
||||
resp.response_header.each do |name, value|
|
||||
resp_headers[name.to_sym] = value
|
||||
end
|
||||
end
|
||||
|
||||
# Finalize the response object with values from `env`.
|
||||
env[:response].finish(env)
|
||||
end
|
||||
|
||||
# Execute single request.
|
||||
else
|
||||
client = nil
|
||||
block = lambda { request.send(http_method, request_config(env)) }
|
||||
|
||||
if !EM.reactor_running?
|
||||
EM.run do
|
||||
Fiber.new {
|
||||
client = block.call
|
||||
EM.stop
|
||||
}.resume
|
||||
end
|
||||
else
|
||||
client = block.call
|
||||
end
|
||||
|
||||
raise client.error if client.error
|
||||
|
||||
status = client.response_header.status
|
||||
reason = client.response_header.http_reason
|
||||
save_response(env, status, client.response, nil, reason) do |resp_headers|
|
||||
client.response_header.each do |name, value|
|
||||
resp_headers[name.to_sym] = value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@app.call env
|
||||
rescue Errno::ECONNREFUSED
|
||||
raise Error::ConnectionFailed, $!
|
||||
rescue EventMachine::Connectify::CONNECTError => err
|
||||
if err.message.include?("Proxy Authentication Required")
|
||||
raise Error::ConnectionFailed, %{407 "Proxy Authentication Required "}
|
||||
else
|
||||
raise Error::ConnectionFailed, err
|
||||
end
|
||||
rescue Errno::ETIMEDOUT => err
|
||||
raise Error::TimeoutError, err
|
||||
rescue RuntimeError => err
|
||||
if err.message == "connection closed by server"
|
||||
raise Error::ConnectionFailed, err
|
||||
else
|
||||
raise
|
||||
end
|
||||
rescue => err
|
||||
if defined?(OpenSSL) && OpenSSL::SSL::SSLError === err
|
||||
raise Faraday::SSLError, err
|
||||
else
|
||||
raise
|
||||
end
|
||||
end
|
||||
|
||||
def create_request(env)
|
||||
EventMachine::HttpRequest.new(Utils::URI(env[:url].to_s), connection_config(env).merge(@connection_options))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require 'faraday/adapter/em_synchrony/parallel_manager'
|
||||
|
||||
begin
|
||||
require 'openssl'
|
||||
rescue LoadError
|
||||
warn "Warning: no such file to load -- openssl. Make sure it is installed if you want HTTPS support"
|
||||
else
|
||||
require 'faraday/adapter/em_http_ssl_patch'
|
||||
end if Faraday::Adapter::EMSynchrony.loaded?
|
@ -1,66 +0,0 @@
|
||||
module Faraday
|
||||
class Adapter
|
||||
class EMSynchrony < Faraday::Adapter
|
||||
class ParallelManager
|
||||
|
||||
# Add requests to queue. The `request` argument should be a
|
||||
# `EM::HttpRequest` object.
|
||||
def add(request, method, *args, &block)
|
||||
queue << {
|
||||
:request => request,
|
||||
:method => method,
|
||||
:args => args,
|
||||
:block => block
|
||||
}
|
||||
end
|
||||
|
||||
# Run all requests on queue with `EM::Synchrony::Multi`, wrapping
|
||||
# it in a reactor and fiber if needed.
|
||||
def run
|
||||
result = nil
|
||||
if !EM.reactor_running?
|
||||
EM.run {
|
||||
Fiber.new do
|
||||
result = perform
|
||||
EM.stop
|
||||
end.resume
|
||||
}
|
||||
else
|
||||
result = perform
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
# The request queue.
|
||||
def queue
|
||||
@queue ||= []
|
||||
end
|
||||
|
||||
# Main `EM::Synchrony::Multi` performer.
|
||||
def perform
|
||||
multi = ::EM::Synchrony::Multi.new
|
||||
|
||||
queue.each do |item|
|
||||
method = "a#{item[:method]}".to_sym
|
||||
|
||||
req = item[:request].send(method, *item[:args])
|
||||
req.callback(&item[:block])
|
||||
|
||||
req_name = "req_#{multi.requests.size}".to_sym
|
||||
multi.add(req_name, req)
|
||||
end
|
||||
|
||||
# Clear the queue, so parallel manager objects can be reused.
|
||||
@queue = []
|
||||
|
||||
# Block fiber until all requests have returned.
|
||||
multi.perform
|
||||
end
|
||||
|
||||
end # ParallelManager
|
||||
end # EMSynchrony
|
||||
end # Adapter
|
||||
end # Faraday
|
@ -1,82 +0,0 @@
|
||||
module Faraday
|
||||
class Adapter
|
||||
class Excon < Faraday::Adapter
|
||||
dependency 'excon'
|
||||
|
||||
def call(env)
|
||||
super
|
||||
|
||||
opts = {}
|
||||
if env[:url].scheme == 'https' && ssl = env[:ssl]
|
||||
opts[:ssl_verify_peer] = !!ssl.fetch(:verify, true)
|
||||
opts[:ssl_ca_path] = ssl[:ca_path] if ssl[:ca_path]
|
||||
opts[:ssl_ca_file] = ssl[:ca_file] if ssl[:ca_file]
|
||||
opts[:client_cert] = ssl[:client_cert] if ssl[:client_cert]
|
||||
opts[:client_key] = ssl[:client_key] if ssl[:client_key]
|
||||
opts[:certificate] = ssl[:certificate] if ssl[:certificate]
|
||||
opts[:private_key] = ssl[:private_key] if ssl[:private_key]
|
||||
opts[:ssl_version] = ssl[:version] if ssl[:version]
|
||||
opts[:ssl_min_version] = ssl[:min_version] if ssl[:min_version]
|
||||
opts[:ssl_max_version] = ssl[:max_version] if ssl[:max_version]
|
||||
|
||||
# https://github.com/geemus/excon/issues/106
|
||||
# https://github.com/jruby/jruby-ossl/issues/19
|
||||
opts[:nonblock] = false
|
||||
end
|
||||
|
||||
if ( req = env[:request] )
|
||||
if req[:timeout]
|
||||
opts[:read_timeout] = req[:timeout]
|
||||
opts[:connect_timeout] = req[:timeout]
|
||||
opts[:write_timeout] = req[:timeout]
|
||||
end
|
||||
|
||||
if req[:open_timeout]
|
||||
opts[:connect_timeout] = req[:open_timeout]
|
||||
end
|
||||
|
||||
if req[:proxy]
|
||||
opts[:proxy] = {
|
||||
:host => req[:proxy][:uri].host,
|
||||
:hostname => req[:proxy][:uri].hostname,
|
||||
:port => req[:proxy][:uri].port,
|
||||
:scheme => req[:proxy][:uri].scheme,
|
||||
:user => req[:proxy][:user],
|
||||
:password => req[:proxy][:password]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
conn = create_connection(env, opts)
|
||||
|
||||
resp = conn.request \
|
||||
:method => env[:method].to_s.upcase,
|
||||
:headers => env[:request_headers],
|
||||
:body => read_body(env)
|
||||
|
||||
save_response(env, resp.status.to_i, resp.body, resp.headers, resp.reason_phrase)
|
||||
|
||||
@app.call env
|
||||
rescue ::Excon::Errors::SocketError => err
|
||||
if err.message =~ /\btimeout\b/
|
||||
raise Error::TimeoutError, err
|
||||
elsif err.message =~ /\bcertificate\b/
|
||||
raise Faraday::SSLError, err
|
||||
else
|
||||
raise Error::ConnectionFailed, err
|
||||
end
|
||||
rescue ::Excon::Errors::Timeout => err
|
||||
raise Error::TimeoutError, err
|
||||
end
|
||||
|
||||
def create_connection(env, opts)
|
||||
::Excon.new(env[:url].to_s, opts.merge(@connection_options))
|
||||
end
|
||||
|
||||
# TODO: support streaming requests
|
||||
def read_body(env)
|
||||
env[:body].respond_to?(:read) ? env[:body].read : env[:body]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,128 +0,0 @@
|
||||
module Faraday
|
||||
class Adapter
|
||||
class HTTPClient < Faraday::Adapter
|
||||
dependency 'httpclient'
|
||||
|
||||
def client
|
||||
@client ||= ::HTTPClient.new
|
||||
end
|
||||
|
||||
def call(env)
|
||||
super
|
||||
|
||||
# enable compression
|
||||
client.transparent_gzip_decompression = true
|
||||
|
||||
if req = env[:request]
|
||||
if proxy = req[:proxy]
|
||||
configure_proxy proxy
|
||||
end
|
||||
|
||||
if bind = req[:bind]
|
||||
configure_socket bind
|
||||
end
|
||||
|
||||
configure_timeouts req
|
||||
end
|
||||
|
||||
if env[:url].scheme == 'https' && ssl = env[:ssl]
|
||||
configure_ssl ssl
|
||||
end
|
||||
|
||||
configure_client
|
||||
|
||||
# TODO Don't stream yet.
|
||||
# https://github.com/nahi/httpclient/pull/90
|
||||
env[:body] = env[:body].read if env[:body].respond_to? :read
|
||||
|
||||
resp = client.request env[:method], env[:url],
|
||||
:body => env[:body],
|
||||
:header => env[:request_headers]
|
||||
|
||||
save_response env, resp.status, resp.body, resp.headers, resp.reason
|
||||
|
||||
@app.call env
|
||||
rescue ::HTTPClient::TimeoutError, Errno::ETIMEDOUT
|
||||
raise Faraday::Error::TimeoutError, $!
|
||||
rescue ::HTTPClient::BadResponseError => err
|
||||
if err.message.include?('status 407')
|
||||
raise Faraday::Error::ConnectionFailed, %{407 "Proxy Authentication Required "}
|
||||
else
|
||||
raise Faraday::Error::ClientError, $!
|
||||
end
|
||||
rescue Errno::ECONNREFUSED, IOError, SocketError
|
||||
raise Faraday::Error::ConnectionFailed, $!
|
||||
rescue => err
|
||||
if defined?(OpenSSL) && OpenSSL::SSL::SSLError === err
|
||||
raise Faraday::SSLError, err
|
||||
else
|
||||
raise
|
||||
end
|
||||
end
|
||||
|
||||
def configure_socket(bind)
|
||||
client.socket_local.host = bind[:host]
|
||||
client.socket_local.port = bind[:port]
|
||||
end
|
||||
|
||||
def configure_proxy(proxy)
|
||||
client.proxy = proxy[:uri]
|
||||
if proxy[:user] && proxy[:password]
|
||||
client.set_proxy_auth proxy[:user], proxy[:password]
|
||||
end
|
||||
end
|
||||
|
||||
def configure_ssl(ssl)
|
||||
ssl_config = client.ssl_config
|
||||
ssl_config.verify_mode = ssl_verify_mode(ssl)
|
||||
ssl_config.cert_store = ssl_cert_store(ssl)
|
||||
|
||||
ssl_config.add_trust_ca ssl[:ca_file] if ssl[:ca_file]
|
||||
ssl_config.add_trust_ca ssl[:ca_path] if ssl[:ca_path]
|
||||
ssl_config.client_cert = ssl[:client_cert] if ssl[:client_cert]
|
||||
ssl_config.client_key = ssl[:client_key] if ssl[:client_key]
|
||||
ssl_config.verify_depth = ssl[:verify_depth] if ssl[:verify_depth]
|
||||
end
|
||||
|
||||
def configure_timeouts(req)
|
||||
if req[:timeout]
|
||||
client.connect_timeout = req[:timeout]
|
||||
client.receive_timeout = req[:timeout]
|
||||
client.send_timeout = req[:timeout]
|
||||
end
|
||||
|
||||
if req[:open_timeout]
|
||||
client.connect_timeout = req[:open_timeout]
|
||||
client.send_timeout = req[:open_timeout]
|
||||
end
|
||||
end
|
||||
|
||||
def configure_client
|
||||
@config_block.call(client) if @config_block
|
||||
end
|
||||
|
||||
def ssl_cert_store(ssl)
|
||||
return ssl[:cert_store] if ssl[:cert_store]
|
||||
# Memoize the cert store so that the same one is passed to
|
||||
# HTTPClient each time, to avoid resyncing SSL sesions when
|
||||
# it's changed
|
||||
@cert_store ||= begin
|
||||
# Use the default cert store by default, i.e. system ca certs
|
||||
cert_store = OpenSSL::X509::Store.new
|
||||
cert_store.set_default_paths
|
||||
cert_store
|
||||
end
|
||||
end
|
||||
|
||||
def ssl_verify_mode(ssl)
|
||||
ssl[:verify_mode] || begin
|
||||
if ssl.fetch(:verify, true)
|
||||
OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
|
||||
else
|
||||
OpenSSL::SSL::VERIFY_NONE
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,152 +0,0 @@
|
||||
begin
|
||||
require 'net/https'
|
||||
rescue LoadError
|
||||
warn "Warning: no such file to load -- net/https. Make sure openssl is installed if you want ssl support"
|
||||
require 'net/http'
|
||||
end
|
||||
require 'zlib'
|
||||
|
||||
module Faraday
|
||||
class Adapter
|
||||
class NetHttp < Faraday::Adapter
|
||||
NET_HTTP_EXCEPTIONS = [
|
||||
IOError,
|
||||
Errno::ECONNABORTED,
|
||||
Errno::ECONNREFUSED,
|
||||
Errno::ECONNRESET,
|
||||
Errno::EHOSTUNREACH,
|
||||
Errno::EINVAL,
|
||||
Errno::ENETUNREACH,
|
||||
Errno::EPIPE,
|
||||
Net::HTTPBadResponse,
|
||||
Net::HTTPHeaderSyntaxError,
|
||||
Net::ProtocolError,
|
||||
SocketError,
|
||||
Zlib::GzipFile::Error,
|
||||
]
|
||||
|
||||
NET_HTTP_EXCEPTIONS << OpenSSL::SSL::SSLError if defined?(OpenSSL)
|
||||
NET_HTTP_EXCEPTIONS << Net::OpenTimeout if defined?(Net::OpenTimeout)
|
||||
|
||||
def initialize(app = nil, opts = {}, &block)
|
||||
@cert_store = nil
|
||||
super(app, opts, &block)
|
||||
end
|
||||
|
||||
def call(env)
|
||||
super
|
||||
with_net_http_connection(env) do |http|
|
||||
configure_ssl(http, env[:ssl]) if env[:url].scheme == 'https' and env[:ssl]
|
||||
configure_request(http, env[:request])
|
||||
|
||||
begin
|
||||
http_response = perform_request(http, env)
|
||||
rescue *NET_HTTP_EXCEPTIONS => err
|
||||
if defined?(OpenSSL) && OpenSSL::SSL::SSLError === err
|
||||
raise Faraday::SSLError, err
|
||||
else
|
||||
raise Error::ConnectionFailed, err
|
||||
end
|
||||
end
|
||||
|
||||
save_response(env, http_response.code.to_i, http_response.body || '', nil, http_response.message) do |response_headers|
|
||||
http_response.each_header do |key, value|
|
||||
response_headers[key] = value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@app.call env
|
||||
rescue Timeout::Error, Errno::ETIMEDOUT => err
|
||||
raise Faraday::Error::TimeoutError, err
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_request(env)
|
||||
request = Net::HTTPGenericRequest.new \
|
||||
env[:method].to_s.upcase, # request method
|
||||
!!env[:body], # is there request body
|
||||
:head != env[:method], # is there response body
|
||||
env[:url].request_uri, # request uri path
|
||||
env[:request_headers] # request headers
|
||||
|
||||
if env[:body].respond_to?(:read)
|
||||
request.body_stream = env[:body]
|
||||
else
|
||||
request.body = env[:body]
|
||||
end
|
||||
request
|
||||
end
|
||||
|
||||
def perform_request(http, env)
|
||||
if :get == env[:method] and !env[:body]
|
||||
# prefer `get` to `request` because the former handles gzip (ruby 1.9)
|
||||
http.get env[:url].request_uri, env[:request_headers]
|
||||
else
|
||||
http.request create_request(env)
|
||||
end
|
||||
end
|
||||
|
||||
def with_net_http_connection(env)
|
||||
yield net_http_connection(env)
|
||||
end
|
||||
|
||||
def net_http_connection(env)
|
||||
if proxy = env[:request][:proxy]
|
||||
Net::HTTP::Proxy(proxy[:uri].hostname, proxy[:uri].port, proxy[:user], proxy[:password])
|
||||
else
|
||||
Net::HTTP
|
||||
end.new(env[:url].hostname, env[:url].port || (env[:url].scheme == 'https' ? 443 : 80))
|
||||
end
|
||||
|
||||
def configure_ssl(http, ssl)
|
||||
http.use_ssl = true
|
||||
http.verify_mode = ssl_verify_mode(ssl)
|
||||
http.cert_store = ssl_cert_store(ssl)
|
||||
|
||||
http.cert = ssl[:client_cert] if ssl[:client_cert]
|
||||
http.key = ssl[:client_key] if ssl[:client_key]
|
||||
http.ca_file = ssl[:ca_file] if ssl[:ca_file]
|
||||
http.ca_path = ssl[:ca_path] if ssl[:ca_path]
|
||||
http.verify_depth = ssl[:verify_depth] if ssl[:verify_depth]
|
||||
http.ssl_version = ssl[:version] if ssl[:version]
|
||||
http.min_version = ssl[:min_version] if ssl[:min_version]
|
||||
http.max_version = ssl[:max_version] if ssl[:max_version]
|
||||
end
|
||||
|
||||
def configure_request(http, req)
|
||||
if req[:timeout]
|
||||
http.read_timeout = req[:timeout]
|
||||
http.open_timeout = req[:timeout]
|
||||
http.write_timeout = req[:timeout] if http.respond_to?(:write_timeout=)
|
||||
end
|
||||
http.open_timeout = req[:open_timeout] if req[:open_timeout]
|
||||
http.write_timeout = req[:write_timeout] if req[:write_timeout] && http.respond_to?(:write_timeout=)
|
||||
# Only set if Net::Http supports it, since Ruby 2.5.
|
||||
http.max_retries = 0 if http.respond_to?(:max_retries=)
|
||||
|
||||
@config_block.call(http) if @config_block
|
||||
end
|
||||
|
||||
def ssl_cert_store(ssl)
|
||||
return ssl[:cert_store] if ssl[:cert_store]
|
||||
return @cert_store if @cert_store
|
||||
# Use the default cert store by default, i.e. system ca certs
|
||||
@cert_store = OpenSSL::X509::Store.new
|
||||
@cert_store.set_default_paths
|
||||
@cert_store
|
||||
end
|
||||
|
||||
def ssl_verify_mode(ssl)
|
||||
ssl[:verify_mode] || begin
|
||||
if ssl.fetch(:verify, true)
|
||||
OpenSSL::SSL::VERIFY_PEER
|
||||
else
|
||||
OpenSSL::SSL::VERIFY_NONE
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,68 +0,0 @@
|
||||
module Faraday
|
||||
class Adapter
|
||||
class NetHttpPersistent < NetHttp
|
||||
dependency 'net/http/persistent'
|
||||
|
||||
private
|
||||
|
||||
def net_http_connection(env)
|
||||
@cached_connection ||=
|
||||
if Net::HTTP::Persistent.instance_method(:initialize).parameters.first == [:key, :name]
|
||||
options = {name: 'Faraday'}
|
||||
options[:pool_size] = @connection_options[:pool_size] if @connection_options.key?(:pool_size)
|
||||
Net::HTTP::Persistent.new(options)
|
||||
else
|
||||
Net::HTTP::Persistent.new('Faraday')
|
||||
end
|
||||
|
||||
proxy_uri = proxy_uri(env)
|
||||
@cached_connection.proxy = proxy_uri if @cached_connection.proxy_uri != proxy_uri
|
||||
@cached_connection
|
||||
end
|
||||
|
||||
def proxy_uri(env)
|
||||
proxy_uri = nil
|
||||
if (proxy = env[:request][:proxy])
|
||||
proxy_uri = ::URI::HTTP === proxy[:uri] ? proxy[:uri].dup : ::URI.parse(proxy[:uri].to_s)
|
||||
proxy_uri.user = proxy_uri.password = nil
|
||||
# awful patch for net-http-persistent 2.8 not unescaping user/password
|
||||
(class << proxy_uri; self; end).class_eval do
|
||||
define_method(:user) { proxy[:user] }
|
||||
define_method(:password) { proxy[:password] }
|
||||
end if proxy[:user]
|
||||
end
|
||||
proxy_uri
|
||||
end
|
||||
|
||||
def perform_request(http, env)
|
||||
http.request env[:url], create_request(env)
|
||||
rescue Errno::ETIMEDOUT => error
|
||||
raise Faraday::Error::TimeoutError, error
|
||||
rescue Net::HTTP::Persistent::Error => error
|
||||
if error.message.include? 'Timeout'
|
||||
raise Faraday::Error::TimeoutError, error
|
||||
elsif error.message.include? 'connection refused'
|
||||
raise Faraday::Error::ConnectionFailed, error
|
||||
else
|
||||
raise
|
||||
end
|
||||
end
|
||||
|
||||
def configure_ssl(http, ssl)
|
||||
http_set(http, :verify_mode, ssl_verify_mode(ssl))
|
||||
http_set(http, :cert_store, ssl_cert_store(ssl))
|
||||
|
||||
http_set(http, :certificate, ssl[:client_cert]) if ssl[:client_cert]
|
||||
http_set(http, :private_key, ssl[:client_key]) if ssl[:client_key]
|
||||
http_set(http, :ca_file, ssl[:ca_file]) if ssl[:ca_file]
|
||||
http_set(http, :ssl_version, ssl[:version]) if ssl[:version]
|
||||
end
|
||||
|
||||
def http_set(http, attr, value)
|
||||
if http.send(attr) != value
|
||||
http.send("#{attr}=", value)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,95 +0,0 @@
|
||||
module Faraday
|
||||
class Adapter
|
||||
class Patron < Faraday::Adapter
|
||||
dependency 'patron'
|
||||
|
||||
def call(env)
|
||||
super
|
||||
# TODO: support streaming requests
|
||||
env[:body] = env[:body].read if env[:body].respond_to? :read
|
||||
|
||||
session = ::Patron::Session.new
|
||||
@config_block.call(session) if @config_block
|
||||
configure_ssl(session, env[:ssl]) if env[:url].scheme == 'https' and env[:ssl]
|
||||
|
||||
if req = env[:request]
|
||||
session.timeout = session.connect_timeout = req[:timeout] if req[:timeout]
|
||||
session.connect_timeout = req[:open_timeout] if req[:open_timeout]
|
||||
|
||||
if proxy = req[:proxy]
|
||||
proxy_uri = proxy[:uri].dup
|
||||
proxy_uri.user = proxy[:user] && Utils.escape(proxy[:user]).gsub('+', '%20')
|
||||
proxy_uri.password = proxy[:password] && Utils.escape(proxy[:password]).gsub('+', '%20')
|
||||
session.proxy = proxy_uri.to_s
|
||||
end
|
||||
end
|
||||
|
||||
response = begin
|
||||
data = env[:body] ? env[:body].to_s : nil
|
||||
session.request(env[:method], env[:url].to_s, env[:request_headers], :data => data)
|
||||
rescue Errno::ECONNREFUSED, ::Patron::ConnectionFailed
|
||||
raise Error::ConnectionFailed, $!
|
||||
end
|
||||
|
||||
# Remove the "HTTP/1.1 200", leaving just the reason phrase
|
||||
reason_phrase = response.status_line.gsub(/^.* \d{3} /, '')
|
||||
|
||||
save_response(env, response.status, response.body, response.headers, reason_phrase)
|
||||
|
||||
@app.call env
|
||||
rescue ::Patron::TimeoutError => err
|
||||
if connection_timed_out_message?(err.message)
|
||||
raise Faraday::Error::ConnectionFailed, err
|
||||
else
|
||||
raise Faraday::Error::TimeoutError, err
|
||||
end
|
||||
rescue ::Patron::Error => err
|
||||
if err.message.include?("code 407")
|
||||
raise Error::ConnectionFailed, %{407 "Proxy Authentication Required "}
|
||||
else
|
||||
raise Error::ConnectionFailed, err
|
||||
end
|
||||
end
|
||||
|
||||
if loaded? && defined?(::Patron::Request::VALID_ACTIONS)
|
||||
# HAX: helps but doesn't work completely
|
||||
# https://github.com/toland/patron/issues/34
|
||||
::Patron::Request::VALID_ACTIONS.tap do |actions|
|
||||
if actions[0].is_a?(Symbol)
|
||||
actions << :patch unless actions.include? :patch
|
||||
actions << :options unless actions.include? :options
|
||||
else
|
||||
# Patron 0.4.20 and up
|
||||
actions << "PATCH" unless actions.include? "PATCH"
|
||||
actions << "OPTIONS" unless actions.include? "OPTIONS"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def configure_ssl(session, ssl)
|
||||
if ssl.fetch(:verify, true)
|
||||
session.cacert = ssl[:ca_file]
|
||||
else
|
||||
session.insecure = true
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
CURL_TIMEOUT_MESSAGES = [ "Connection time-out",
|
||||
"Connection timed out",
|
||||
"Timed out before name resolve",
|
||||
"server connect has timed out",
|
||||
"Resolving timed out",
|
||||
"name lookup timed out",
|
||||
"timed out before SSL",
|
||||
"connect() timed out"
|
||||
].freeze
|
||||
|
||||
def connection_timed_out_message?(message)
|
||||
CURL_TIMEOUT_MESSAGES.any? { |curl_message| message.include?(curl_message) }
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
@ -1,58 +0,0 @@
|
||||
module Faraday
|
||||
class Adapter
|
||||
# Sends requests to a Rack app.
|
||||
#
|
||||
# Examples
|
||||
#
|
||||
# class MyRackApp
|
||||
# def call(env)
|
||||
# [200, {'Content-Type' => 'text/html'}, ["hello world"]]
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Faraday.new do |conn|
|
||||
# conn.adapter :rack, MyRackApp.new
|
||||
# end
|
||||
class Rack < Faraday::Adapter
|
||||
dependency 'rack/test'
|
||||
|
||||
# not prefixed with "HTTP_"
|
||||
SPECIAL_HEADERS = %w[ CONTENT_LENGTH CONTENT_TYPE ]
|
||||
|
||||
def initialize(faraday_app, rack_app)
|
||||
super(faraday_app)
|
||||
mock_session = ::Rack::MockSession.new(rack_app)
|
||||
@session = ::Rack::Test::Session.new(mock_session)
|
||||
end
|
||||
|
||||
def call(env)
|
||||
super
|
||||
rack_env = {
|
||||
:method => env[:method],
|
||||
:input => env[:body].respond_to?(:read) ? env[:body].read : env[:body],
|
||||
'rack.url_scheme' => env[:url].scheme
|
||||
}
|
||||
|
||||
env[:request_headers].each do |name, value|
|
||||
name = name.upcase.tr('-', '_')
|
||||
name = "HTTP_#{name}" unless SPECIAL_HEADERS.include? name
|
||||
rack_env[name] = value
|
||||
end if env[:request_headers]
|
||||
|
||||
timeout = env[:request][:timeout] || env[:request][:open_timeout]
|
||||
response = if timeout
|
||||
Timer.timeout(timeout, Faraday::Error::TimeoutError) { execute_request(env, rack_env) }
|
||||
else
|
||||
execute_request(env, rack_env)
|
||||
end
|
||||
|
||||
save_response(env, response.status, response.body, response.headers)
|
||||
@app.call env
|
||||
end
|
||||
|
||||
def execute_request(env, rack_env)
|
||||
@session.request(env[:url].to_s, rack_env)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,51 +1,78 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'timeout'
|
||||
|
||||
module Faraday
|
||||
class Adapter
|
||||
# Examples
|
||||
#
|
||||
# @example
|
||||
# test = Faraday::Connection.new do
|
||||
# use Faraday::Adapter::Test do |stub|
|
||||
# # simply define matcher to match the request
|
||||
# # Define matcher to match the request
|
||||
# stub.get '/resource.json' do
|
||||
# # return static content
|
||||
# [200, {'Content-Type' => 'application/json'}, 'hi world']
|
||||
# end
|
||||
#
|
||||
#
|
||||
# # response with content generated based on request
|
||||
# stub.get '/showget' do |env|
|
||||
# [200, {'Content-Type' => 'text/plain'}, env[:method].to_s]
|
||||
# end
|
||||
#
|
||||
# # regular expression can be used as matching filter
|
||||
#
|
||||
# # A regular expression can be used as matching filter
|
||||
# stub.get /\A\/items\/(\d+)\z/ do |env, meta|
|
||||
# # in case regular expression is used an instance of MatchData can be received
|
||||
# [200, {'Content-Type' => 'text/plain'}, "showing item: #{meta[:match_data][1]}"]
|
||||
# # in case regular expression is used, an instance of MatchData
|
||||
# # can be received
|
||||
# [200,
|
||||
# {'Content-Type' => 'text/plain'},
|
||||
# "showing item: #{meta[:match_data][1]}"
|
||||
# ]
|
||||
# end
|
||||
#
|
||||
# # Test the request body is the same as the stubbed body
|
||||
# stub.post('/bar', 'name=YK&word=call') { [200, {}, ''] }
|
||||
#
|
||||
# # You can pass a proc as a stubbed body and check the request body in your way.
|
||||
# # In this case, the proc should return true or false.
|
||||
# stub.post('/foo', ->(request_body) do
|
||||
# JSON.parse(request_body).slice('name') == { 'name' => 'YK' } }) { [200, {}, '']
|
||||
# end
|
||||
#
|
||||
# # You can set strict_mode to exactly match the stubbed requests.
|
||||
# stub.strict_mode = true
|
||||
# end
|
||||
# end
|
||||
#
|
||||
#
|
||||
# resp = test.get '/resource.json'
|
||||
# resp.body # => 'hi world'
|
||||
#
|
||||
#
|
||||
# resp = test.get '/showget'
|
||||
# resp.body # => 'get'
|
||||
#
|
||||
#
|
||||
# resp = test.get '/items/1'
|
||||
# resp.body # => 'showing item: 1'
|
||||
#
|
||||
#
|
||||
# resp = test.get '/items/2'
|
||||
# resp.body # => 'showing item: 2'
|
||||
#
|
||||
|
||||
# resp = test.post '/bar', 'name=YK&word=call'
|
||||
# resp.status # => 200
|
||||
#
|
||||
# resp = test.post '/foo', JSON.dump(name: 'YK', created_at: Time.now)
|
||||
# resp.status # => 200
|
||||
class Test < Faraday::Adapter
|
||||
attr_accessor :stubs
|
||||
|
||||
# A stack of Stubs
|
||||
class Stubs
|
||||
class NotFound < StandardError
|
||||
end
|
||||
|
||||
def initialize
|
||||
# {:get => [Stub, Stub]}
|
||||
@stack, @consumed = {}, {}
|
||||
def initialize(strict_mode: false)
|
||||
# { get: [Stub, Stub] }
|
||||
@stack = {}
|
||||
@consumed = {}
|
||||
@strict_mode = strict_mode
|
||||
@stubs_mutex = Monitor.new
|
||||
yield(self) if block_given?
|
||||
end
|
||||
|
||||
@ -53,17 +80,23 @@ module Faraday
|
||||
@stack.empty?
|
||||
end
|
||||
|
||||
def match(request_method, host, path, headers, body)
|
||||
return false if !@stack.key?(request_method)
|
||||
# @param env [Faraday::Env]
|
||||
def match(env)
|
||||
request_method = env[:method]
|
||||
return false unless @stack.key?(request_method)
|
||||
|
||||
stack = @stack[request_method]
|
||||
consumed = (@consumed[request_method] ||= [])
|
||||
|
||||
stub, meta = matches?(stack, host, path, headers, body)
|
||||
if stub
|
||||
consumed << stack.delete(stub)
|
||||
return stub, meta
|
||||
@stubs_mutex.synchronize do
|
||||
stub, meta = matches?(stack, env)
|
||||
if stub
|
||||
removed = stack.delete(stub)
|
||||
consumed << removed unless removed.nil?
|
||||
return stub, meta
|
||||
end
|
||||
end
|
||||
matches?(consumed, host, path, headers, body)
|
||||
matches?(consumed, env)
|
||||
end
|
||||
|
||||
def get(path, headers = {}, &block)
|
||||
@ -74,15 +107,15 @@ module Faraday
|
||||
new_stub(:head, path, headers, &block)
|
||||
end
|
||||
|
||||
def post(path, body=nil, headers = {}, &block)
|
||||
def post(path, body = nil, headers = {}, &block)
|
||||
new_stub(:post, path, headers, body, &block)
|
||||
end
|
||||
|
||||
def put(path, body=nil, headers = {}, &block)
|
||||
def put(path, body = nil, headers = {}, &block)
|
||||
new_stub(:put, path, headers, body, &block)
|
||||
end
|
||||
|
||||
def patch(path, body=nil, headers = {}, &block)
|
||||
def patch(path, body = nil, headers = {}, &block)
|
||||
new_stub(:patch, path, headers, body, &block)
|
||||
end
|
||||
|
||||
@ -98,87 +131,131 @@ module Faraday
|
||||
def verify_stubbed_calls
|
||||
failed_stubs = []
|
||||
@stack.each do |method, stubs|
|
||||
unless stubs.size == 0
|
||||
failed_stubs.concat(stubs.map {|stub|
|
||||
next if stubs.empty?
|
||||
|
||||
failed_stubs.concat(
|
||||
stubs.map do |stub|
|
||||
"Expected #{method} #{stub}."
|
||||
})
|
||||
end
|
||||
)
|
||||
end
|
||||
raise failed_stubs.join(' ') unless failed_stubs.empty?
|
||||
end
|
||||
|
||||
# Set strict_mode. If the value is true, this adapter tries to find matched requests strictly,
|
||||
# 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_value do |stubs|
|
||||
stubs.each do |stub|
|
||||
stub.strict_mode = value
|
||||
end
|
||||
end
|
||||
raise failed_stubs.join(" ") unless failed_stubs.size == 0
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def new_stub(request_method, path, headers = {}, body=nil, &block)
|
||||
def new_stub(request_method, path, headers = {}, body = nil, &block)
|
||||
normalized_path, host =
|
||||
if path.is_a?(Regexp)
|
||||
path
|
||||
else
|
||||
[Faraday::Utils.normalize_path(path), Faraday::Utils.URI(path).host]
|
||||
[
|
||||
Faraday::Utils.normalize_path(path),
|
||||
Faraday::Utils.URI(path).host
|
||||
]
|
||||
end
|
||||
path, query = normalized_path.respond_to?(:split) ? normalized_path.split('?') : normalized_path
|
||||
headers = Utils::Headers.new(headers)
|
||||
|
||||
(@stack[request_method] ||= []) << Stub.new(host, normalized_path, headers, body, block)
|
||||
stub = Stub.new(host, path, query, headers, body, @strict_mode, block)
|
||||
(@stack[request_method] ||= []) << stub
|
||||
end
|
||||
|
||||
def matches?(stack, host, path, headers, body)
|
||||
# @param stack [Hash]
|
||||
# @param env [Faraday::Env]
|
||||
def matches?(stack, env)
|
||||
stack.each do |stub|
|
||||
match_result, meta = stub.matches?(host, path, headers, body)
|
||||
match_result, meta = stub.matches?(env)
|
||||
return stub, meta if match_result
|
||||
end
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
class Stub < Struct.new(:host, :path, :params, :headers, :body, :block)
|
||||
def initialize(host, full, headers, body, block)
|
||||
path, query = full.respond_to?(:split) ? full.split("?") : full
|
||||
params = query ?
|
||||
Faraday::Utils.parse_nested_query(query) :
|
||||
{}
|
||||
super(host, path, params, headers, body, block)
|
||||
end
|
||||
# Stub request
|
||||
Stub = Struct.new(:host, :path, :query, :headers, :body, :strict_mode, :block) do
|
||||
# @param env [Faraday::Env]
|
||||
def matches?(env)
|
||||
request_host = env[:url].host
|
||||
request_path = Faraday::Utils.normalize_path(env[:url].path)
|
||||
request_headers = env.request_headers
|
||||
request_body = env[:body]
|
||||
|
||||
def matches?(request_host, request_uri, request_headers, request_body)
|
||||
request_path, request_query = request_uri.split('?')
|
||||
request_params = request_query ?
|
||||
Faraday::Utils.parse_nested_query(request_query) :
|
||||
{}
|
||||
# meta is a hash use as carrier
|
||||
# meta is a hash used as carrier
|
||||
# that will be yielded to consumer block
|
||||
meta = {}
|
||||
return (host.nil? || host == request_host) &&
|
||||
[(host.nil? || host == request_host) &&
|
||||
path_match?(request_path, meta) &&
|
||||
params_match?(request_params) &&
|
||||
(body.to_s.size.zero? || request_body == body) &&
|
||||
headers_match?(request_headers), meta
|
||||
params_match?(env) &&
|
||||
body_match?(request_body) &&
|
||||
headers_match?(request_headers), meta]
|
||||
end
|
||||
|
||||
def path_match?(request_path, meta)
|
||||
if path.is_a? Regexp
|
||||
if path.is_a?(Regexp)
|
||||
!!(meta[:match_data] = path.match(request_path))
|
||||
else
|
||||
path == request_path
|
||||
end
|
||||
end
|
||||
|
||||
def params_match?(request_params)
|
||||
# @param env [Faraday::Env]
|
||||
def params_match?(env)
|
||||
request_params = env[:params]
|
||||
params = env.params_encoder.decode(query) || {}
|
||||
|
||||
if strict_mode
|
||||
return Set.new(params) == Set.new(request_params)
|
||||
end
|
||||
|
||||
params.keys.all? do |key|
|
||||
request_params[key] == params[key]
|
||||
end
|
||||
end
|
||||
|
||||
def headers_match?(request_headers)
|
||||
if strict_mode
|
||||
headers_with_user_agent = headers.dup.tap do |hs|
|
||||
# NOTE: Set User-Agent in case it's not set when creating Stubs.
|
||||
# Users would not want to set Faraday's User-Agent explicitly.
|
||||
hs[:user_agent] ||= Connection::USER_AGENT
|
||||
end
|
||||
return Set.new(headers_with_user_agent) == Set.new(request_headers)
|
||||
end
|
||||
|
||||
headers.keys.all? do |key|
|
||||
request_headers[key] == headers[key]
|
||||
end
|
||||
end
|
||||
|
||||
def body_match?(request_body)
|
||||
return true if body.to_s.empty?
|
||||
|
||||
case body
|
||||
when Proc
|
||||
body.call(request_body)
|
||||
else
|
||||
request_body == body
|
||||
end
|
||||
end
|
||||
|
||||
def to_s
|
||||
"#{path} #{body}"
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(app, stubs=nil, &block)
|
||||
def initialize(app, stubs = nil, &block)
|
||||
super(app)
|
||||
@stubs = stubs || Stubs.new
|
||||
configure(&block) if block
|
||||
@ -188,26 +265,47 @@ module Faraday
|
||||
yield(stubs)
|
||||
end
|
||||
|
||||
# @param env [Faraday::Env]
|
||||
def call(env)
|
||||
super
|
||||
host = env[:url].host
|
||||
normalized_path = Faraday::Utils.normalize_path(env[:url])
|
||||
params_encoder = env.request.params_encoder || Faraday::Utils.default_params_encoder
|
||||
|
||||
stub, meta = stubs.match(env[:method], host, normalized_path, env.request_headers, env[:body])
|
||||
if stub
|
||||
env[:params] = (query = env[:url].query) ?
|
||||
params_encoder.decode(query) : {}
|
||||
block_arity = stub.block.arity
|
||||
status, headers, body = (block_arity >= 0) ?
|
||||
stub.block.call(*[env, meta].take(block_arity)) :
|
||||
stub.block.call(env, meta)
|
||||
save_response(env, status, body, headers)
|
||||
else
|
||||
raise Stubs::NotFound, "no stubbed request for #{env[:method]} #{normalized_path} #{env[:body]}"
|
||||
env.request.params_encoder ||= Faraday::Utils.default_params_encoder
|
||||
env[:params] = env.params_encoder.decode(env[:url].query) || {}
|
||||
stub, meta = stubs.match(env)
|
||||
|
||||
unless stub
|
||||
raise Stubs::NotFound, "no stubbed request for #{env[:method]} " \
|
||||
"#{env[:url]} #{env[:body]} #{env[:headers]}"
|
||||
end
|
||||
|
||||
block_arity = stub.block.arity
|
||||
params = if block_arity >= 0
|
||||
[env, meta].take(block_arity)
|
||||
else
|
||||
[env, meta]
|
||||
end
|
||||
|
||||
timeout = request_timeout(:open, env[:request])
|
||||
timeout ||= request_timeout(:read, env[:request])
|
||||
|
||||
status, headers, body =
|
||||
if timeout
|
||||
::Timeout.timeout(timeout, Faraday::TimeoutError) do
|
||||
stub.block.call(*params)
|
||||
end
|
||||
else
|
||||
stub.block.call(*params)
|
||||
end
|
||||
|
||||
# We need to explicitly pass `reason_phrase = nil` here to avoid keyword args conflicts.
|
||||
# See https://github.com/lostisland/faraday/issues/1444
|
||||
# TODO: remove `nil` explicit reason_phrase once Ruby 3.0 becomes minimum req. version
|
||||
save_response(env, status, body, headers, nil)
|
||||
|
||||
@app.call(env)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Faraday::Adapter.register_middleware(test: Faraday::Adapter::Test)
|
||||
|
@ -1,12 +0,0 @@
|
||||
module Faraday
|
||||
class Adapter
|
||||
# This class is just a stub, the real adapter is in https://github.com/philsturgeon/typhoeus/blob/master/lib/typhoeus/adapters/faraday.rb
|
||||
class Typhoeus < Faraday::Adapter
|
||||
# Needs to define this method in order to support Typhoeus <= 1.3.0
|
||||
def call; end
|
||||
|
||||
dependency 'typhoeus'
|
||||
dependency 'typhoeus/adapters/faraday'
|
||||
end
|
||||
end
|
||||
end
|
30
lib/faraday/adapter_registry.rb
Normal file
@ -0,0 +1,30 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'monitor'
|
||||
|
||||
module Faraday
|
||||
# AdapterRegistry registers adapter class names so they can be looked up by a
|
||||
# String or Symbol name.
|
||||
class AdapterRegistry
|
||||
def initialize
|
||||
@lock = Monitor.new
|
||||
@constants = {}
|
||||
end
|
||||
|
||||
def get(name)
|
||||
klass = @lock.synchronize do
|
||||
@constants[name]
|
||||
end
|
||||
return klass if klass
|
||||
|
||||
Object.const_get(name).tap { |c| set(c, name) }
|
||||
end
|
||||
|
||||
def set(klass, name = nil)
|
||||
name ||= klass.to_s
|
||||
@lock.synchronize do
|
||||
@constants[name] = klass
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,84 +0,0 @@
|
||||
module Faraday
|
||||
# Internal: Adds the ability for other modules to manage autoloadable
|
||||
# constants.
|
||||
module AutoloadHelper
|
||||
# Internal: Registers the constants to be auto loaded.
|
||||
#
|
||||
# prefix - The String require prefix. If the path is inside Faraday, then
|
||||
# it will be prefixed with the root path of this loaded Faraday
|
||||
# version.
|
||||
# options - Hash of Symbol => String library names.
|
||||
#
|
||||
# Examples.
|
||||
#
|
||||
# Faraday.autoload_all 'faraday/foo',
|
||||
# :Bar => 'bar'
|
||||
#
|
||||
# # requires faraday/foo/bar to load Faraday::Bar.
|
||||
# Faraday::Bar
|
||||
#
|
||||
#
|
||||
# Returns nothing.
|
||||
def autoload_all(prefix, options)
|
||||
if prefix =~ /^faraday(\/|$)/i
|
||||
prefix = File.join(Faraday.root_path, prefix)
|
||||
end
|
||||
options.each do |const_name, path|
|
||||
autoload const_name, File.join(prefix, path)
|
||||
end
|
||||
end
|
||||
|
||||
# Internal: Loads each autoloaded constant. If thread safety is a concern,
|
||||
# wrap this in a Mutex.
|
||||
#
|
||||
# Returns nothing.
|
||||
def load_autoloaded_constants
|
||||
constants.each do |const|
|
||||
const_get(const) if autoload?(const)
|
||||
end
|
||||
end
|
||||
|
||||
# Internal: Filters the module's contents with those that have been already
|
||||
# autoloaded.
|
||||
#
|
||||
# Returns an Array of Class/Module objects.
|
||||
def all_loaded_constants
|
||||
constants.map { |c| const_get(c) }.
|
||||
select { |a| a.respond_to?(:loaded?) && a.loaded? }
|
||||
end
|
||||
end
|
||||
|
||||
class Adapter
|
||||
extend AutoloadHelper
|
||||
autoload_all 'faraday/adapter',
|
||||
:NetHttp => 'net_http',
|
||||
:NetHttpPersistent => 'net_http_persistent',
|
||||
:EMSynchrony => 'em_synchrony',
|
||||
:EMHttp => 'em_http',
|
||||
:Typhoeus => 'typhoeus',
|
||||
:Patron => 'patron',
|
||||
:Excon => 'excon',
|
||||
:Test => 'test',
|
||||
:Rack => 'rack',
|
||||
:HTTPClient => 'httpclient'
|
||||
end
|
||||
|
||||
class Request
|
||||
extend AutoloadHelper
|
||||
autoload_all 'faraday/request',
|
||||
:UrlEncoded => 'url_encoded',
|
||||
:Multipart => 'multipart',
|
||||
:Retry => 'retry',
|
||||
:Authorization => 'authorization',
|
||||
:BasicAuthentication => 'basic_authentication',
|
||||
:TokenAuthentication => 'token_authentication',
|
||||
:Instrumentation => 'instrumentation'
|
||||
end
|
||||
|
||||
class Response
|
||||
extend AutoloadHelper
|
||||
autoload_all 'faraday/response',
|
||||
:RaiseError => 'raise_error',
|
||||
:Logger => 'logger'
|
||||
end
|
||||
end
|
@ -1,67 +1,70 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Faraday
|
||||
# Public: Connection objects manage the default properties and the middleware
|
||||
# Connection objects manage the default properties and the middleware
|
||||
# stack for fulfilling an HTTP request.
|
||||
#
|
||||
# Examples
|
||||
# @example
|
||||
#
|
||||
# conn = Faraday::Connection.new 'http://sushi.com'
|
||||
# conn = Faraday::Connection.new 'http://httpbingo.org'
|
||||
#
|
||||
# # GET http://sushi.com/nigiri
|
||||
# # GET http://httpbingo.org/nigiri
|
||||
# conn.get 'nigiri'
|
||||
# # => #<Faraday::Response>
|
||||
#
|
||||
class Connection
|
||||
# A Set of allowed HTTP verbs.
|
||||
METHODS = Set.new [:get, :post, :put, :delete, :head, :patch, :options]
|
||||
METHODS = Set.new %i[get post put delete head patch options trace]
|
||||
USER_AGENT = "Faraday v#{VERSION}".freeze
|
||||
|
||||
# Public: Returns a Hash of URI query unencoded key/value pairs.
|
||||
# @return [Hash] URI query unencoded key/value pairs.
|
||||
attr_reader :params
|
||||
|
||||
# Public: Returns a Hash of unencoded HTTP header key/value pairs.
|
||||
# @return [Hash] unencoded HTTP header key/value pairs.
|
||||
attr_reader :headers
|
||||
|
||||
# Public: Returns a URI with the prefix used for all requests from this
|
||||
# Connection. This includes a default host name, scheme, port, and path.
|
||||
# @return [String] a URI with the prefix used for all requests from this
|
||||
# Connection. This includes a default host name, scheme, port, and path.
|
||||
attr_reader :url_prefix
|
||||
|
||||
# Public: Returns the Faraday::Builder for this Connection.
|
||||
# @return [Faraday::RackBuilder] Builder for this Connection.
|
||||
attr_reader :builder
|
||||
|
||||
# Public: Returns a Hash of the request options.
|
||||
attr_reader :options
|
||||
|
||||
# Public: Returns a Hash of the SSL options.
|
||||
# @return [Hash] SSL options.
|
||||
attr_reader :ssl
|
||||
|
||||
# Public: Returns the parallel manager for this Connection.
|
||||
# @return [Object] the parallel manager for this Connection.
|
||||
attr_reader :parallel_manager
|
||||
|
||||
# Public: Sets the default parallel manager for this connection.
|
||||
# Sets the default parallel manager for this connection.
|
||||
attr_writer :default_parallel_manager
|
||||
|
||||
# Public: Gets or Sets the Hash proxy options.
|
||||
# attr_reader :proxy
|
||||
# @return [Hash] proxy options.
|
||||
attr_reader :proxy
|
||||
|
||||
# Public: Initializes a new Faraday::Connection.
|
||||
# Initializes a new Faraday::Connection.
|
||||
#
|
||||
# url - URI or String base URL to use as a prefix for all
|
||||
# @param url [URI, String] URI or String base URL to use as a prefix for all
|
||||
# requests (optional).
|
||||
# options - Hash or Faraday::ConnectionOptions.
|
||||
# :url - URI or String base URL (default: "http:/").
|
||||
# :params - Hash of URI query unencoded key/value pairs.
|
||||
# :headers - Hash of unencoded HTTP header key/value pairs.
|
||||
# :request - Hash of request options.
|
||||
# :ssl - Hash of SSL options.
|
||||
# :proxy - URI, String or Hash of HTTP proxy options
|
||||
# (default: "http_proxy" environment variable).
|
||||
# :uri - URI or String
|
||||
# :user - String (optional)
|
||||
# :password - String (optional)
|
||||
# @param options [Hash, Faraday::ConnectionOptions]
|
||||
# @option options [URI, String] :url ('http:/') URI or String base URL
|
||||
# @option options [Hash<String => String>] :params URI query unencoded
|
||||
# key/value pairs.
|
||||
# @option options [Hash<String => String>] :headers Hash of unencoded HTTP
|
||||
# header key/value pairs.
|
||||
# @option options [Hash] :request Hash of request options.
|
||||
# @option options [Hash] :ssl Hash of SSL options.
|
||||
# @option options [Hash, URI, String] :proxy proxy options, either as a URL
|
||||
# or as a Hash
|
||||
# @option options [URI, String] :proxy[:uri]
|
||||
# @option options [String] :proxy[:user]
|
||||
# @option options [String] :proxy[:password]
|
||||
# @yield [self] after all setup has been done
|
||||
def initialize(url = nil, options = nil)
|
||||
options = ConnectionOptions.from(options)
|
||||
|
||||
if url.is_a?(Hash) || url.is_a?(ConnectionOptions)
|
||||
options = options.merge(url)
|
||||
options = Utils.deep_merge(options, url)
|
||||
url = options.url
|
||||
end
|
||||
|
||||
@ -71,10 +74,11 @@ module Faraday
|
||||
@options = options.request
|
||||
@ssl = options.ssl
|
||||
@default_parallel_manager = options.parallel_manager
|
||||
@manual_proxy = nil
|
||||
|
||||
@builder = options.builder || begin
|
||||
# pass an empty block to Builder so it doesn't assume default middleware
|
||||
options.new_builder(block_given? ? Proc.new { |b| } : nil)
|
||||
options.new_builder(block_given? ? proc { |b| } : nil)
|
||||
end
|
||||
|
||||
self.url_prefix = url || 'http:/'
|
||||
@ -82,94 +86,195 @@ module Faraday
|
||||
@params.update(options.params) if options.params
|
||||
@headers.update(options.headers) if options.headers
|
||||
|
||||
@manual_proxy = !!options.proxy
|
||||
@proxy = options.proxy ? ProxyOptions.from(options.proxy) : proxy_from_env(url)
|
||||
@temp_proxy = @proxy
|
||||
initialize_proxy(url, options)
|
||||
|
||||
yield(self) if block_given?
|
||||
|
||||
@headers[:user_agent] ||= "Faraday v#{VERSION}"
|
||||
@headers[:user_agent] ||= USER_AGENT
|
||||
end
|
||||
|
||||
# Public: Sets the Hash of URI query unencoded key/value pairs.
|
||||
def initialize_proxy(url, options)
|
||||
@manual_proxy = !!options.proxy
|
||||
@proxy =
|
||||
if options.proxy
|
||||
ProxyOptions.from(options.proxy)
|
||||
else
|
||||
proxy_from_env(url)
|
||||
end
|
||||
end
|
||||
|
||||
# Sets the Hash of URI query unencoded key/value pairs.
|
||||
# @param hash [Hash]
|
||||
def params=(hash)
|
||||
@params.replace hash
|
||||
end
|
||||
|
||||
# Public: Sets the Hash of unencoded HTTP header key/value pairs.
|
||||
# Sets the Hash of unencoded HTTP header key/value pairs.
|
||||
# @param hash [Hash]
|
||||
def headers=(hash)
|
||||
@headers.replace hash
|
||||
end
|
||||
|
||||
extend Forwardable
|
||||
|
||||
def_delegators :builder, :build, :use, :request, :response, :adapter, :app
|
||||
def_delegators :builder, :use, :request, :response, :adapter, :app
|
||||
|
||||
# Public: Makes an HTTP request without a body.
|
||||
# Closes the underlying resources and/or connections. In the case of
|
||||
# persistent connections, this closes all currently open connections
|
||||
# but does not prevent new connections from being made.
|
||||
def close
|
||||
app.close
|
||||
end
|
||||
|
||||
# @!method get(url = nil, params = nil, headers = nil)
|
||||
# Makes a GET HTTP request without a body.
|
||||
# @!scope class
|
||||
#
|
||||
# url - The optional String base URL to use as a prefix for all
|
||||
# requests. Can also be the options Hash.
|
||||
# params - Hash of URI query unencoded key/value pairs.
|
||||
# headers - Hash of unencoded HTTP header key/value pairs.
|
||||
# @param url [String, URI, nil] The optional String base URL to use as a prefix for
|
||||
# all requests. Can also be the options Hash.
|
||||
# @param params [Hash, nil] Hash of URI query unencoded key/value pairs.
|
||||
# @param headers [Hash, nil] unencoded HTTP header key/value pairs.
|
||||
#
|
||||
# Examples
|
||||
#
|
||||
# conn.get '/items', {:page => 1}, :accept => 'application/json'
|
||||
# conn.head '/items/1'
|
||||
# @example
|
||||
# conn.get '/items', { page: 1 }, :accept => 'application/json'
|
||||
#
|
||||
# # ElasticSearch example sending a body with GET.
|
||||
# conn.get '/twitter/tweet/_search' do |req|
|
||||
# req.headers[:content_type] = 'application/json'
|
||||
# req.params[:routing] = 'kimchy'
|
||||
# req.body = JSON.generate(:query => {...})
|
||||
# req.body = JSON.generate(query: {...})
|
||||
# end
|
||||
#
|
||||
# Yields a Faraday::Request for further request customizations.
|
||||
# Returns a Faraday::Response.
|
||||
# @yield [Faraday::Request] for further request customizations
|
||||
# @return [Faraday::Response]
|
||||
|
||||
# @!method head(url = nil, params = nil, headers = nil)
|
||||
# Makes a HEAD HTTP request without a body.
|
||||
# @!scope class
|
||||
#
|
||||
# Signature
|
||||
# @param url [String, URI, nil] The optional String base URL to use as a prefix for
|
||||
# all requests. Can also be the options Hash.
|
||||
# @param params [Hash, nil] Hash of URI query unencoded key/value pairs.
|
||||
# @param headers [Hash, nil] unencoded HTTP header key/value pairs.
|
||||
#
|
||||
# <verb>(url = nil, params = nil, headers = nil)
|
||||
# @example
|
||||
# conn.head '/items/1'
|
||||
#
|
||||
# verb - An HTTP verb: get, head, or delete.
|
||||
%w[get head delete].each do |method|
|
||||
# @yield [Faraday::Request] for further request customizations
|
||||
# @return [Faraday::Response]
|
||||
|
||||
# @!method delete(url = nil, params = nil, headers = nil)
|
||||
# Makes a DELETE HTTP request without a body.
|
||||
# @!scope class
|
||||
#
|
||||
# @param url [String, URI, nil] The optional String base URL to use as a prefix for
|
||||
# all requests. Can also be the options Hash.
|
||||
# @param params [Hash, nil] Hash of URI query unencoded key/value pairs.
|
||||
# @param headers [Hash, nil] unencoded HTTP header key/value pairs.
|
||||
#
|
||||
# @example
|
||||
# conn.delete '/items/1'
|
||||
#
|
||||
# @yield [Faraday::Request] for further request customizations
|
||||
# @return [Faraday::Response]
|
||||
|
||||
# @!method trace(url = nil, params = nil, headers = nil)
|
||||
# Makes a TRACE HTTP request without a body.
|
||||
# @!scope class
|
||||
#
|
||||
# @param url [String, URI, nil] The optional String base URL to use as a prefix for
|
||||
# all requests. Can also be the options Hash.
|
||||
# @param params [Hash, nil] Hash of URI query unencoded key/value pairs.
|
||||
# @param headers [Hash, nil] unencoded HTTP header key/value pairs.
|
||||
#
|
||||
# @example
|
||||
# conn.connect '/items/1'
|
||||
#
|
||||
# @yield [Faraday::Request] for further request customizations
|
||||
# @return [Faraday::Response]
|
||||
|
||||
# @!visibility private
|
||||
METHODS_WITH_QUERY.each do |method|
|
||||
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
||||
def #{method}(url = nil, params = nil, headers = nil)
|
||||
run_request(:#{method}, url, nil, headers) { |request|
|
||||
run_request(:#{method}, url, nil, headers) do |request|
|
||||
request.params.update(params) if params
|
||||
yield(request) if block_given?
|
||||
}
|
||||
yield request if block_given?
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
|
||||
# Public: Makes an HTTP request with a body.
|
||||
# @overload options()
|
||||
# Returns current Connection options.
|
||||
#
|
||||
# url - The optional String base URL to use as a prefix for all
|
||||
# requests. Can also be the options Hash.
|
||||
# body - The String body for the request.
|
||||
# headers - Hash of unencoded HTTP header key/value pairs.
|
||||
# @overload options(url, params = nil, headers = nil)
|
||||
# Makes an OPTIONS HTTP request to the given URL.
|
||||
# @param url [String, URI, nil] String base URL to sue as a prefix for all requests.
|
||||
# @param params [Hash, nil] Hash of URI query unencoded key/value pairs.
|
||||
# @param headers [Hash, nil] unencoded HTTP header key/value pairs.
|
||||
#
|
||||
# Examples
|
||||
# @example
|
||||
# conn.options '/items/1'
|
||||
#
|
||||
# conn.post '/items', data, :content_type => 'application/json'
|
||||
# @yield [Faraday::Request] for further request customizations
|
||||
# @return [Faraday::Response]
|
||||
def options(*args)
|
||||
return @options if args.empty?
|
||||
|
||||
url, params, headers = *args
|
||||
run_request(:options, url, nil, headers) do |request|
|
||||
request.params.update(params) if params
|
||||
yield request if block_given?
|
||||
end
|
||||
end
|
||||
|
||||
# @!method post(url = nil, body = nil, headers = nil)
|
||||
# Makes a POST HTTP request with a body.
|
||||
# @!scope class
|
||||
#
|
||||
# @param url [String, URI, nil] The optional String base URL to use as a prefix for
|
||||
# all requests. Can also be the options Hash.
|
||||
# @param body [String, nil] body for the request.
|
||||
# @param headers [Hash, nil] unencoded HTTP header key/value pairs.
|
||||
#
|
||||
# @example
|
||||
# conn.post '/items', data, content_type: 'application/json'
|
||||
#
|
||||
# # Simple ElasticSearch indexing sample.
|
||||
# conn.post '/twitter/tweet' do |req|
|
||||
# req.headers[:content_type] = 'application/json'
|
||||
# req.params[:routing] = 'kimchy'
|
||||
# req.body = JSON.generate(:user => 'kimchy', ...)
|
||||
# req.body = JSON.generate(user: 'kimchy', ...)
|
||||
# end
|
||||
#
|
||||
# Yields a Faraday::Request for further request customizations.
|
||||
# Returns a Faraday::Response.
|
||||
# @yield [Faraday::Request] for further request customizations
|
||||
# @return [Faraday::Response]
|
||||
|
||||
# @!method put(url = nil, body = nil, headers = nil)
|
||||
# Makes a PUT HTTP request with a body.
|
||||
# @!scope class
|
||||
#
|
||||
# Signature
|
||||
# @param url [String, URI, nil] The optional String base URL to use as a prefix for
|
||||
# all requests. Can also be the options Hash.
|
||||
# @param body [String, nil] body for the request.
|
||||
# @param headers [Hash, nil] unencoded HTTP header key/value pairs.
|
||||
#
|
||||
# <verb>(url = nil, body = nil, headers = nil)
|
||||
# @example
|
||||
# conn.put '/products/123', data, content_type: 'application/json'
|
||||
#
|
||||
# verb - An HTTP verb: post, put, or patch.
|
||||
%w[post put patch].each do |method|
|
||||
# # Star a gist.
|
||||
# conn.put 'https://api.github.com/gists/GIST_ID/star' do |req|
|
||||
# req.headers['Accept'] = 'application/vnd.github+json'
|
||||
# req.headers['Authorization'] = 'Bearer <YOUR-TOKEN>'
|
||||
# req.headers['X-GitHub-Api-Version'] = '2022-11-28'
|
||||
# end
|
||||
#
|
||||
# @yield [Faraday::Request] for further request customizations
|
||||
# @return [Faraday::Response]
|
||||
|
||||
# @!visibility private
|
||||
METHODS_WITH_BODY.each do |method|
|
||||
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
||||
def #{method}(url = nil, body = nil, headers = nil, &block)
|
||||
run_request(:#{method}, url, body, headers, &block)
|
||||
@ -177,116 +282,62 @@ module Faraday
|
||||
RUBY
|
||||
end
|
||||
|
||||
# Public: Sets up the Authorization header with these credentials, encoded
|
||||
# with base64.
|
||||
# Check if the adapter is parallel-capable.
|
||||
#
|
||||
# login - The authentication login.
|
||||
# pass - The authentication password.
|
||||
# @yield if the adapter isn't parallel-capable, or if no adapter is set yet.
|
||||
#
|
||||
# Examples
|
||||
#
|
||||
# conn.basic_auth 'Aladdin', 'open sesame'
|
||||
# conn.headers['Authorization']
|
||||
# # => "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
|
||||
#
|
||||
# Returns nothing.
|
||||
def basic_auth(login, pass)
|
||||
set_authorization_header(:basic_auth, login, pass)
|
||||
end
|
||||
|
||||
# Public: Sets up the Authorization header with the given token.
|
||||
#
|
||||
# token - The String token.
|
||||
# options - Optional Hash of extra token options.
|
||||
#
|
||||
# Examples
|
||||
#
|
||||
# conn.token_auth 'abcdef', :foo => 'bar'
|
||||
# conn.headers['Authorization']
|
||||
# # => "Token token=\"abcdef\",
|
||||
# foo=\"bar\""
|
||||
#
|
||||
# Returns nothing.
|
||||
def token_auth(token, options = nil)
|
||||
set_authorization_header(:token_auth, token, options)
|
||||
end
|
||||
|
||||
# Public: Sets up a custom Authorization header.
|
||||
#
|
||||
# type - The String authorization type.
|
||||
# token - The String or Hash token. A String value is taken literally, and
|
||||
# a Hash is encoded into comma separated key/value pairs.
|
||||
#
|
||||
# Examples
|
||||
#
|
||||
# conn.authorization :Bearer, 'mF_9.B5f-4.1JqM'
|
||||
# conn.headers['Authorization']
|
||||
# # => "Bearer mF_9.B5f-4.1JqM"
|
||||
#
|
||||
# conn.authorization :Token, :token => 'abcdef', :foo => 'bar'
|
||||
# conn.headers['Authorization']
|
||||
# # => "Token token=\"abcdef\",
|
||||
# foo=\"bar\""
|
||||
#
|
||||
# Returns nothing.
|
||||
def authorization(type, token)
|
||||
set_authorization_header(:authorization, type, token)
|
||||
end
|
||||
|
||||
# Internal: Traverse the middleware stack in search of a
|
||||
# parallel-capable adapter.
|
||||
#
|
||||
# Yields in case of not found.
|
||||
#
|
||||
# Returns a parallel manager or nil if not found.
|
||||
# @return [Object, nil] a parallel manager or nil if yielded
|
||||
# @api private
|
||||
def default_parallel_manager
|
||||
@default_parallel_manager ||= begin
|
||||
handler = @builder.handlers.detect do |h|
|
||||
h.klass.respond_to?(:supports_parallel?) and h.klass.supports_parallel?
|
||||
end
|
||||
adapter = @builder.adapter.klass if @builder.adapter
|
||||
|
||||
if handler
|
||||
handler.klass.setup_parallel_manager
|
||||
if support_parallel?(adapter)
|
||||
adapter.setup_parallel_manager
|
||||
elsif block_given?
|
||||
yield
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Public: Determine if this Faraday::Connection can make parallel requests.
|
||||
# Determine if this Faraday::Connection can make parallel requests.
|
||||
#
|
||||
# Returns true or false.
|
||||
# @return [Boolean]
|
||||
def in_parallel?
|
||||
!!@parallel_manager
|
||||
end
|
||||
|
||||
# Public: Sets up the parallel manager to make a set of requests.
|
||||
# Sets up the parallel manager to make a set of requests.
|
||||
#
|
||||
# manager - The parallel manager that this Connection's Adapter uses.
|
||||
# @param manager [Object] The parallel manager that this Connection's
|
||||
# Adapter uses.
|
||||
#
|
||||
# Yields a block to execute multiple requests.
|
||||
# Returns nothing.
|
||||
def in_parallel(manager = nil)
|
||||
@parallel_manager = manager || default_parallel_manager {
|
||||
warn "Warning: `in_parallel` called but no parallel-capable adapter on Faraday stack"
|
||||
warn caller[2,10].join("\n")
|
||||
# @yield a block to execute multiple requests.
|
||||
# @return [void]
|
||||
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
|
||||
}
|
||||
yield
|
||||
@parallel_manager && @parallel_manager.run
|
||||
end
|
||||
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
|
||||
|
||||
# Public: Gets or Sets the Hash proxy options.
|
||||
def proxy(arg = nil)
|
||||
return @proxy if arg.nil?
|
||||
warn 'Warning: use of proxy(new_value) to set connection proxy have been DEPRECATED and will be removed in Faraday 1.0'
|
||||
@manual_proxy = true
|
||||
@proxy = ProxyOptions.from(arg)
|
||||
end
|
||||
|
||||
# Public: Sets the Hash proxy options.
|
||||
# Sets the Hash proxy options.
|
||||
#
|
||||
# @param new_value [Object]
|
||||
def proxy=(new_value)
|
||||
@manual_proxy = true
|
||||
@proxy = new_value ? ProxyOptions.from(new_value) : nil
|
||||
@ -295,22 +346,21 @@ module Faraday
|
||||
def_delegators :url_prefix, :scheme, :scheme=, :host, :host=, :port, :port=
|
||||
def_delegator :url_prefix, :path, :path_prefix
|
||||
|
||||
# Public: Parses the giving url with URI and stores the individual
|
||||
# components in this connection. These components serve as defaults for
|
||||
# Parses the given URL with URI and stores the individual
|
||||
# components in this connection. These components serve as defaults for
|
||||
# requests made by this connection.
|
||||
#
|
||||
# url - A String or URI.
|
||||
# @param url [String, URI]
|
||||
# @param encoder [Object]
|
||||
#
|
||||
# Examples
|
||||
# @example
|
||||
#
|
||||
# conn = Faraday::Connection.new { ... }
|
||||
# conn.url_prefix = "https://sushi.com/api"
|
||||
# conn.url_prefix = "https://httpbingo.org/api"
|
||||
# conn.scheme # => https
|
||||
# conn.path_prefix # => "/api"
|
||||
#
|
||||
# conn.get("nigiri?page=2") # accesses https://sushi.com/api/nigiri
|
||||
#
|
||||
# Returns the parsed URI from the given input..
|
||||
# conn.get("nigiri?page=2") # accesses https://httpbingo.org/api/nigiri
|
||||
def url_prefix=(url, encoder = nil)
|
||||
uri = @url_prefix = Utils.URI(url)
|
||||
self.path_prefix = uri.path
|
||||
@ -319,65 +369,80 @@ module Faraday
|
||||
uri.query = nil
|
||||
|
||||
with_uri_credentials(uri) do |user, password|
|
||||
basic_auth user, password
|
||||
set_basic_auth(user, password)
|
||||
uri.user = uri.password = nil
|
||||
end
|
||||
|
||||
uri
|
||||
@proxy = proxy_from_env(url) unless @manual_proxy
|
||||
end
|
||||
|
||||
# Public: Sets the path prefix and ensures that it always has a leading
|
||||
def set_basic_auth(user, password)
|
||||
header = Faraday::Utils.basic_header_from(user, password)
|
||||
headers[Faraday::Request::Authorization::KEY] = header
|
||||
end
|
||||
|
||||
# Sets the path prefix and ensures that it always has a leading
|
||||
# slash.
|
||||
#
|
||||
# value - A String.
|
||||
# @param value [String]
|
||||
#
|
||||
# Returns the new String path prefix.
|
||||
# @return [String] the new path prefix
|
||||
def path_prefix=(value)
|
||||
url_prefix.path = if value
|
||||
value = '/' + value unless value[0,1] == '/'
|
||||
value
|
||||
end
|
||||
value = "/#{value}" unless value[0, 1] == '/'
|
||||
value
|
||||
end
|
||||
end
|
||||
|
||||
# Public: Takes a relative url for a request and combines it with the defaults
|
||||
# Takes a relative url for a request and combines it with the defaults
|
||||
# set on the connection instance.
|
||||
#
|
||||
# @param url [String, URI, nil]
|
||||
# @param extra_params [Hash]
|
||||
#
|
||||
# @example
|
||||
# conn = Faraday::Connection.new { ... }
|
||||
# conn.url_prefix = "https://sushi.com/api?token=abc"
|
||||
# conn.url_prefix = "https://httpbingo.org/api?token=abc"
|
||||
# conn.scheme # => https
|
||||
# conn.path_prefix # => "/api"
|
||||
#
|
||||
# conn.build_url("nigiri?page=2") # => https://sushi.com/api/nigiri?token=abc&page=2
|
||||
# conn.build_url("nigiri", :page => 2) # => https://sushi.com/api/nigiri?token=abc&page=2
|
||||
# conn.build_url("nigiri?page=2")
|
||||
# # => https://httpbingo.org/api/nigiri?token=abc&page=2
|
||||
#
|
||||
# conn.build_url("nigiri", page: 2)
|
||||
# # => https://httpbingo.org/api/nigiri?token=abc&page=2
|
||||
#
|
||||
def build_url(url = nil, extra_params = nil)
|
||||
uri = build_exclusive_url(url)
|
||||
|
||||
query_values = params.dup.merge_query(uri.query, options.params_encoder)
|
||||
query_values.update extra_params if extra_params
|
||||
uri.query = query_values.empty? ? nil : query_values.to_query(options.params_encoder)
|
||||
query_values.update(extra_params) if extra_params
|
||||
uri.query =
|
||||
if query_values.empty?
|
||||
nil
|
||||
else
|
||||
query_values.to_query(options.params_encoder)
|
||||
end
|
||||
|
||||
uri
|
||||
end
|
||||
|
||||
# Builds and runs the Faraday::Request.
|
||||
#
|
||||
# method - The Symbol HTTP method.
|
||||
# url - The String or URI to access.
|
||||
# body - The request body that will eventually be converted to a string.
|
||||
# headers - Hash of unencoded HTTP header key/value pairs.
|
||||
# @param method [Symbol] HTTP method.
|
||||
# @param url [String, URI, nil] String or URI to access.
|
||||
# @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.
|
||||
#
|
||||
# Returns a Faraday::Response.
|
||||
# @return [Faraday::Response]
|
||||
def run_request(method, url, body, headers)
|
||||
if !METHODS.include?(method)
|
||||
unless METHODS.include?(method)
|
||||
raise ArgumentError, "unknown http method: #{method}"
|
||||
end
|
||||
|
||||
# Resets temp_proxy
|
||||
@temp_proxy = proxy_for_request(url)
|
||||
|
||||
request = build_request(method) do |req|
|
||||
req.options = req.options.merge(:proxy => @temp_proxy)
|
||||
req.options.proxy = proxy_for_request(url)
|
||||
req.url(url) if url
|
||||
req.headers.update(headers) if headers
|
||||
req.body = body if body
|
||||
@ -389,96 +454,111 @@ module Faraday
|
||||
|
||||
# Creates and configures the request object.
|
||||
#
|
||||
# Returns the new Request.
|
||||
# @param method [Symbol]
|
||||
#
|
||||
# @yield [Faraday::Request] if block given
|
||||
# @return [Faraday::Request]
|
||||
def build_request(method)
|
||||
Request.create(method) do |req|
|
||||
req.params = self.params.dup
|
||||
req.headers = self.headers.dup
|
||||
req.options = self.options
|
||||
req.params = params.dup
|
||||
req.headers = headers.dup
|
||||
req.options = options.dup
|
||||
yield(req) if block_given?
|
||||
end
|
||||
end
|
||||
|
||||
# Internal: Build an absolute URL based on url_prefix.
|
||||
# Build an absolute URL based on url_prefix.
|
||||
#
|
||||
# url - A String or URI-like object
|
||||
# params - A Faraday::Utils::ParamsHash to replace the query values
|
||||
# @param url [String, URI, nil]
|
||||
# @param params [Faraday::Utils::ParamsHash] A Faraday::Utils::ParamsHash to
|
||||
# replace the query values
|
||||
# of the resulting url (default: nil).
|
||||
#
|
||||
# Returns the resulting URI instance.
|
||||
# @return [URI]
|
||||
def build_exclusive_url(url = nil, params = nil, params_encoder = nil)
|
||||
url = nil if url.respond_to?(:empty?) and url.empty?
|
||||
base = url_prefix
|
||||
if url and base.path and base.path !~ /\/$/
|
||||
base = base.dup
|
||||
base.path = base.path + '/' # ensure trailing slash
|
||||
url = nil if url.respond_to?(:empty?) && url.empty?
|
||||
base = url_prefix.dup
|
||||
if url && !base.path.end_with?('/')
|
||||
base.path = "#{base.path}/" # ensure trailing slash
|
||||
end
|
||||
# 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
|
||||
uri.query = params.to_query(params_encoder || options.params_encoder) if params
|
||||
uri.query = nil if uri.query and uri.query.empty?
|
||||
if params
|
||||
uri.query = params.to_query(params_encoder || options.params_encoder)
|
||||
end
|
||||
uri.query = nil if uri.query && uri.query.empty?
|
||||
uri
|
||||
end
|
||||
|
||||
# Internal: Creates a duplicate of this Faraday::Connection.
|
||||
# Creates a duplicate of this Faraday::Connection.
|
||||
#
|
||||
# Returns a Faraday::Connection.
|
||||
# @api private
|
||||
#
|
||||
# @return [Faraday::Connection]
|
||||
def dup
|
||||
self.class.new(build_exclusive_url,
|
||||
:headers => headers.dup,
|
||||
:params => params.dup,
|
||||
:builder => builder.dup,
|
||||
:ssl => ssl.dup,
|
||||
:request => options.dup)
|
||||
headers: headers.dup,
|
||||
params: params.dup,
|
||||
builder: builder.dup,
|
||||
ssl: ssl.dup,
|
||||
request: options.dup)
|
||||
end
|
||||
|
||||
# Internal: Yields username and password extracted from a URI if they both exist.
|
||||
# Yields username and password extracted from a URI if they both exist.
|
||||
#
|
||||
# @param uri [URI]
|
||||
# @yield [username, password] any username and password
|
||||
# @yieldparam username [String] any username from URI
|
||||
# @yieldparam password [String] any password from URI
|
||||
# @return [void]
|
||||
# @api private
|
||||
def with_uri_credentials(uri)
|
||||
if uri.user and uri.password
|
||||
yield(Utils.unescape(uri.user), Utils.unescape(uri.password))
|
||||
end
|
||||
end
|
||||
return unless uri.user && uri.password
|
||||
|
||||
def set_authorization_header(header_type, *args)
|
||||
header = Faraday::Request.lookup_middleware(header_type).
|
||||
header(*args)
|
||||
headers[Faraday::Request::Authorization::KEY] = header
|
||||
yield(Utils.unescape(uri.user), Utils.unescape(uri.password))
|
||||
end
|
||||
|
||||
def proxy_from_env(url)
|
||||
return if Faraday.ignore_env_proxy
|
||||
|
||||
uri = nil
|
||||
if URI.parse('').respond_to?(:find_proxy)
|
||||
case url
|
||||
when String
|
||||
uri = Utils.URI(url)
|
||||
uri = URI.parse("#{uri.scheme}://#{uri.hostname}").find_proxy
|
||||
when URI
|
||||
uri = url.find_proxy
|
||||
when nil
|
||||
uri = find_default_proxy
|
||||
end
|
||||
else
|
||||
warn 'no_proxy is unsupported' if ENV['no_proxy'] || ENV['NO_PROXY']
|
||||
case url
|
||||
when String
|
||||
uri = Utils.URI(url)
|
||||
uri = if uri.host.nil?
|
||||
find_default_proxy
|
||||
else
|
||||
URI.parse("#{uri.scheme}://#{uri.host}").find_proxy
|
||||
end
|
||||
when URI
|
||||
uri = url.find_proxy
|
||||
when nil
|
||||
uri = find_default_proxy
|
||||
end
|
||||
ProxyOptions.from(uri) if uri
|
||||
end
|
||||
|
||||
def find_default_proxy
|
||||
uri = ENV['http_proxy']
|
||||
if uri && !uri.empty?
|
||||
uri = 'http://' + uri if uri !~ /^http/i
|
||||
uri
|
||||
end
|
||||
uri = ENV.fetch('http_proxy', nil)
|
||||
return unless uri && !uri.empty?
|
||||
|
||||
uri = "http://#{uri}" unless uri.match?(/^http/i)
|
||||
uri
|
||||
end
|
||||
|
||||
def proxy_for_request(url)
|
||||
return self.proxy if @manual_proxy
|
||||
return proxy if @manual_proxy
|
||||
|
||||
if url && Utils.URI(url).absolute?
|
||||
proxy_from_env(url)
|
||||
else
|
||||
self.proxy
|
||||
proxy
|
||||
end
|
||||
end
|
||||
|
||||
def support_parallel?(adapter)
|
||||
adapter.respond_to?(:supports_parallel?) && adapter&.supports_parallel?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
105
lib/faraday/encoders/flat_params_encoder.rb
Normal file
@ -0,0 +1,105 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Faraday
|
||||
# FlatParamsEncoder manages URI params as a flat hash. Any Array values repeat
|
||||
# the parameter multiple times.
|
||||
module FlatParamsEncoder
|
||||
class << self
|
||||
extend Forwardable
|
||||
def_delegators :'Faraday::Utils', :escape, :unescape
|
||||
end
|
||||
|
||||
# Encode converts the given param into a URI querystring. Keys and values
|
||||
# will converted to strings and appropriately escaped for the URI.
|
||||
#
|
||||
# @param params [Hash] query arguments to convert.
|
||||
#
|
||||
# @example
|
||||
#
|
||||
# encode({a: %w[one two three], b: true, c: "C"})
|
||||
# # => 'a=one&a=two&a=three&b=true&c=C'
|
||||
#
|
||||
# @return [String] the URI querystring (without the leading '?')
|
||||
def self.encode(params)
|
||||
return nil if params.nil?
|
||||
|
||||
unless params.is_a?(Array)
|
||||
unless params.respond_to?(:to_hash)
|
||||
raise TypeError,
|
||||
"Can't convert #{params.class} into Hash."
|
||||
end
|
||||
params = params.to_hash
|
||||
params = params.map do |key, value|
|
||||
key = key.to_s if key.is_a?(Symbol)
|
||||
[key, value]
|
||||
end
|
||||
|
||||
# Only to be used for non-Array inputs. Arrays should preserve order.
|
||||
params.sort! if @sort_params
|
||||
end
|
||||
|
||||
# The params have form [['key1', 'value1'], ['key2', 'value2']].
|
||||
buffer = +''
|
||||
params.each do |key, value|
|
||||
encoded_key = escape(key)
|
||||
if value.nil?
|
||||
buffer << "#{encoded_key}&"
|
||||
elsif value.is_a?(Array)
|
||||
if value.empty?
|
||||
buffer << "#{encoded_key}=&"
|
||||
else
|
||||
value.each do |sub_value|
|
||||
encoded_value = escape(sub_value)
|
||||
buffer << "#{encoded_key}=#{encoded_value}&"
|
||||
end
|
||||
end
|
||||
else
|
||||
encoded_value = escape(value)
|
||||
buffer << "#{encoded_key}=#{encoded_value}&"
|
||||
end
|
||||
end
|
||||
buffer.chop
|
||||
end
|
||||
|
||||
# Decode converts the given URI querystring into a hash.
|
||||
#
|
||||
# @param query [String] query arguments to parse.
|
||||
#
|
||||
# @example
|
||||
#
|
||||
# decode('a=one&a=two&a=three&b=true&c=C')
|
||||
# # => {"a"=>["one", "two", "three"], "b"=>"true", "c"=>"C"}
|
||||
#
|
||||
# @return [Hash] parsed keys and value strings from the querystring.
|
||||
def self.decode(query)
|
||||
return nil if query.nil?
|
||||
|
||||
empty_accumulator = {}
|
||||
|
||||
split_query = query.split('&').filter_map do |pair|
|
||||
pair.split('=', 2) if pair && !pair.empty?
|
||||
end
|
||||
split_query.each_with_object(empty_accumulator.dup) do |pair, accu|
|
||||
pair[0] = unescape(pair[0])
|
||||
pair[1] = true if pair[1].nil?
|
||||
if pair[1].respond_to?(:to_str)
|
||||
pair[1] = unescape(pair[1].to_str.tr('+', ' '))
|
||||
end
|
||||
if accu[pair[0]].is_a?(Array)
|
||||
accu[pair[0]] << pair[1]
|
||||
elsif accu[pair[0]]
|
||||
accu[pair[0]] = [accu[pair[0]], pair[1]]
|
||||
else
|
||||
accu[pair[0]] = pair[1]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class << self
|
||||
attr_accessor :sort_params
|
||||
end
|
||||
|
||||
# Useful default for OAuth and caching.
|
||||
@sort_params = true
|
||||
end
|
||||
end
|
183
lib/faraday/encoders/nested_params_encoder.rb
Normal file
@ -0,0 +1,183 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Faraday
|
||||
# Sub-module for encoding parameters into query-string.
|
||||
module EncodeMethods
|
||||
# @param params [nil, Array, #to_hash] parameters to be encoded
|
||||
#
|
||||
# @return [String] the encoded params
|
||||
#
|
||||
# @raise [TypeError] if params can not be converted to a Hash
|
||||
def encode(params)
|
||||
return nil if params.nil?
|
||||
|
||||
unless params.is_a?(Array)
|
||||
unless params.respond_to?(:to_hash)
|
||||
raise TypeError, "Can't convert #{params.class} into Hash."
|
||||
end
|
||||
|
||||
params = params.to_hash
|
||||
params = params.map do |key, value|
|
||||
key = key.to_s if key.is_a?(Symbol)
|
||||
[key, value]
|
||||
end
|
||||
|
||||
# Only to be used for non-Array inputs. Arrays should preserve order.
|
||||
params.sort! if @sort_params
|
||||
end
|
||||
|
||||
# The params have form [['key1', 'value1'], ['key2', 'value2']].
|
||||
buffer = +''
|
||||
params.each do |parent, value|
|
||||
encoded_parent = escape(parent)
|
||||
buffer << "#{encode_pair(encoded_parent, value)}&"
|
||||
end
|
||||
buffer.chop
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def encode_pair(parent, value)
|
||||
if value.is_a?(Hash)
|
||||
encode_hash(parent, value)
|
||||
elsif value.is_a?(Array)
|
||||
encode_array(parent, value)
|
||||
elsif value.nil?
|
||||
parent
|
||||
else
|
||||
encoded_value = escape(value)
|
||||
"#{parent}=#{encoded_value}"
|
||||
end
|
||||
end
|
||||
|
||||
def encode_hash(parent, value)
|
||||
value = value.map { |key, val| [escape(key), val] }.sort
|
||||
|
||||
buffer = +''
|
||||
value.each do |key, val|
|
||||
new_parent = "#{parent}%5B#{key}%5D"
|
||||
buffer << "#{encode_pair(new_parent, val)}&"
|
||||
end
|
||||
buffer.chop
|
||||
end
|
||||
|
||||
def encode_array(parent, value)
|
||||
return "#{parent}%5B%5D" if value.empty?
|
||||
|
||||
buffer = +''
|
||||
value.each_with_index do |val, index|
|
||||
new_parent = if @array_indices
|
||||
"#{parent}%5B#{index}%5D"
|
||||
else
|
||||
"#{parent}%5B%5D"
|
||||
end
|
||||
buffer << "#{encode_pair(new_parent, val)}&"
|
||||
end
|
||||
buffer.chop
|
||||
end
|
||||
end
|
||||
|
||||
# Sub-module for decoding query-string into parameters.
|
||||
module DecodeMethods
|
||||
# @param query [nil, String]
|
||||
#
|
||||
# @return [Array<Array, String>] the decoded params
|
||||
#
|
||||
# @raise [TypeError] if the nesting is incorrect
|
||||
def decode(query)
|
||||
return nil if query.nil?
|
||||
|
||||
params = {}
|
||||
query.split('&').each do |pair|
|
||||
next if pair.empty?
|
||||
|
||||
key, value = pair.split('=', 2)
|
||||
key = unescape(key)
|
||||
value = unescape(value.tr('+', ' ')) if value
|
||||
decode_pair(key, value, params)
|
||||
end
|
||||
|
||||
dehash(params, 0)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
SUBKEYS_REGEX = /[^\[\]]+(?:\]?\[\])?/
|
||||
|
||||
def decode_pair(key, value, context)
|
||||
subkeys = key.scan(SUBKEYS_REGEX)
|
||||
subkeys.each_with_index do |subkey, i|
|
||||
is_array = subkey =~ /[\[\]]+\Z/
|
||||
subkey = Regexp.last_match.pre_match if is_array
|
||||
last_subkey = i == subkeys.length - 1
|
||||
|
||||
context = prepare_context(context, subkey, is_array, last_subkey)
|
||||
add_to_context(is_array, context, value, subkey) if last_subkey
|
||||
end
|
||||
end
|
||||
|
||||
def prepare_context(context, subkey, is_array, last_subkey)
|
||||
if !last_subkey || is_array
|
||||
context = new_context(subkey, is_array, context)
|
||||
end
|
||||
if context.is_a?(Array) && !is_array
|
||||
context = match_context(context, subkey)
|
||||
end
|
||||
context
|
||||
end
|
||||
|
||||
def new_context(subkey, is_array, context)
|
||||
value_type = is_array ? Array : Hash
|
||||
if context[subkey] && !context[subkey].is_a?(value_type)
|
||||
raise TypeError, "expected #{value_type.name} " \
|
||||
"(got #{context[subkey].class.name}) for param `#{subkey}'"
|
||||
end
|
||||
|
||||
context[subkey] ||= value_type.new
|
||||
end
|
||||
|
||||
def match_context(context, subkey)
|
||||
context << {} if !context.last.is_a?(Hash) || context.last.key?(subkey)
|
||||
context.last
|
||||
end
|
||||
|
||||
def add_to_context(is_array, context, value, subkey)
|
||||
is_array ? context << value : context[subkey] = value
|
||||
end
|
||||
|
||||
# Internal: convert a nested hash with purely numeric keys into an array.
|
||||
# FIXME: this is not compatible with Rack::Utils.parse_nested_query
|
||||
# @!visibility private
|
||||
def dehash(hash, depth)
|
||||
hash.each do |key, value|
|
||||
hash[key] = dehash(value, depth + 1) if value.is_a?(Hash)
|
||||
end
|
||||
|
||||
if depth.positive? && !hash.empty? && hash.keys.all? { |k| k =~ /^\d+$/ }
|
||||
hash.sort.map(&:last)
|
||||
else
|
||||
hash
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# This is the default encoder for Faraday requests.
|
||||
# Using this encoder, parameters will be encoded respecting their structure,
|
||||
# so you can send objects such as Arrays or Hashes as parameters
|
||||
# for your requests.
|
||||
module NestedParamsEncoder
|
||||
class << self
|
||||
attr_accessor :sort_params, :array_indices
|
||||
|
||||
extend Forwardable
|
||||
def_delegators :'Faraday::Utils', :escape, :unescape
|
||||
end
|
||||
|
||||
# Useful default for OAuth and caching.
|
||||
@sort_params = true
|
||||
@array_indices = false
|
||||
|
||||
extend EncodeMethods
|
||||
extend DecodeMethods
|
||||
end
|
||||
end
|
@ -1,22 +1,15 @@
|
||||
module Faraday
|
||||
class Error < StandardError; end
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ClientError < Error
|
||||
# Faraday namespace.
|
||||
module Faraday
|
||||
# Faraday error base class.
|
||||
class Error < StandardError
|
||||
attr_reader :response, :wrapped_exception
|
||||
|
||||
def initialize(ex, response = nil)
|
||||
@wrapped_exception = nil
|
||||
@response = response
|
||||
|
||||
if ex.respond_to?(:backtrace)
|
||||
super(ex.message)
|
||||
@wrapped_exception = ex
|
||||
elsif ex.respond_to?(:each_key)
|
||||
super("the server responded with status #{ex[:status]}")
|
||||
@response = ex
|
||||
else
|
||||
super(ex.to_s)
|
||||
end
|
||||
def initialize(exc = nil, response = nil)
|
||||
@wrapped_exception = nil unless defined?(@wrapped_exception)
|
||||
@response = nil unless defined?(@response)
|
||||
super(exc_msg_and_response!(exc, response))
|
||||
end
|
||||
|
||||
def backtrace
|
||||
@ -28,39 +21,179 @@ module Faraday
|
||||
end
|
||||
|
||||
def inspect
|
||||
inner = ''
|
||||
if @wrapped_exception
|
||||
inner << " wrapped=#{@wrapped_exception.inspect}"
|
||||
end
|
||||
if @response
|
||||
inner << " response=#{@response.inspect}"
|
||||
end
|
||||
if inner.empty?
|
||||
inner << " #{super}"
|
||||
end
|
||||
inner = +''
|
||||
inner << " wrapped=#{@wrapped_exception.inspect}" if @wrapped_exception
|
||||
inner << " response=#{@response.inspect}" if @response
|
||||
inner << " #{super}" if inner.empty?
|
||||
%(#<#{self.class}#{inner}>)
|
||||
end
|
||||
end
|
||||
|
||||
class ConnectionFailed < ClientError; end
|
||||
class ResourceNotFound < ClientError; end
|
||||
class ParsingError < ClientError; end
|
||||
def response_status
|
||||
return unless @response
|
||||
|
||||
class TimeoutError < ClientError
|
||||
def initialize(ex = nil)
|
||||
super(ex || "timeout")
|
||||
@response.is_a?(Faraday::Response) ? @response.status : @response[:status]
|
||||
end
|
||||
|
||||
def response_headers
|
||||
return unless @response
|
||||
|
||||
@response.is_a?(Faraday::Response) ? @response.headers : @response[:headers]
|
||||
end
|
||||
|
||||
def response_body
|
||||
return unless @response
|
||||
|
||||
@response.is_a?(Faraday::Response) ? @response.body : @response[:body]
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Pulls out potential parent exception and response hash, storing them in
|
||||
# instance variables.
|
||||
# exc - Either an Exception, a string message, or a response hash.
|
||||
# response - Hash
|
||||
# :status - Optional integer HTTP response status
|
||||
# :headers - String key/value hash of HTTP response header
|
||||
# values.
|
||||
# :body - Optional string HTTP response body.
|
||||
# :request - Hash
|
||||
# :method - Symbol with the request HTTP method.
|
||||
# :url - URI object with the url requested.
|
||||
# :url_path - String with the url path requested.
|
||||
# :params - String key/value hash of query params
|
||||
# present in the request.
|
||||
# :headers - String key/value hash of HTTP request
|
||||
# header values.
|
||||
# :body - String HTTP request body.
|
||||
#
|
||||
# If a subclass has to call this, then it should pass a string message
|
||||
# to `super`. See NilStatusError.
|
||||
def exc_msg_and_response!(exc, response = nil)
|
||||
if @response.nil? && @wrapped_exception.nil?
|
||||
@wrapped_exception, msg, @response = exc_msg_and_response(exc, response)
|
||||
return msg
|
||||
end
|
||||
|
||||
exc.to_s
|
||||
end
|
||||
|
||||
# Pulls out potential parent exception and response hash.
|
||||
def exc_msg_and_response(exc, response = nil)
|
||||
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
|
||||
|
||||
private
|
||||
|
||||
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
|
||||
|
||||
class SSLError < ClientError
|
||||
# Faraday client error class. Represents 4xx status responses.
|
||||
class ClientError < Error
|
||||
end
|
||||
|
||||
class RetriableResponse < ClientError; end
|
||||
|
||||
[:ClientError, :ConnectionFailed, :ResourceNotFound,
|
||||
:ParsingError, :TimeoutError, :SSLError, :RetriableResponse].each do |const|
|
||||
Error.const_set(const, Faraday.const_get(const))
|
||||
# Raised by Faraday::Response::RaiseError in case of a 400 response.
|
||||
class BadRequestError < ClientError
|
||||
end
|
||||
|
||||
# Raised by Faraday::Response::RaiseError in case of a 401 response.
|
||||
class UnauthorizedError < ClientError
|
||||
end
|
||||
|
||||
# Raised by Faraday::Response::RaiseError in case of a 403 response.
|
||||
class ForbiddenError < ClientError
|
||||
end
|
||||
|
||||
# Raised by Faraday::Response::RaiseError in case of a 404 response.
|
||||
class ResourceNotFound < ClientError
|
||||
end
|
||||
|
||||
# Raised by Faraday::Response::RaiseError in case of a 407 response.
|
||||
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
|
||||
|
||||
# Raised by Faraday::Response::RaiseError in case of a 422 response.
|
||||
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
|
||||
|
||||
# A unified client error for timeouts.
|
||||
class TimeoutError < ServerError
|
||||
def initialize(exc = 'timeout', response = nil)
|
||||
super(exc, response)
|
||||
end
|
||||
end
|
||||
|
||||
# Raised by Faraday::Response::RaiseError in case of a nil status in response.
|
||||
class NilStatusError < ServerError
|
||||
def initialize(exc, response = nil)
|
||||
exc_msg_and_response!(exc, response)
|
||||
super('http status could not be derived from the server response')
|
||||
end
|
||||
end
|
||||
|
||||
# A unified error for failed connections.
|
||||
class ConnectionFailed < Error
|
||||
end
|
||||
|
||||
# A unified client error for SSL errors.
|
||||
class SSLError < Error
|
||||
end
|
||||
|
||||
# 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
|
||||
|
118
lib/faraday/logging/formatter.rb
Normal file
@ -0,0 +1,118 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'pp' # This require is necessary for Hash#pretty_inspect to work, do not remove it, people rely on it.
|
||||
|
||||
module Faraday
|
||||
module Logging
|
||||
# Serves as an integration point to customize logging
|
||||
class Formatter
|
||||
extend Forwardable
|
||||
|
||||
DEFAULT_OPTIONS = { headers: true, bodies: false, errors: false,
|
||||
log_level: :info }.freeze
|
||||
|
||||
def initialize(logger:, options:)
|
||||
@logger = logger
|
||||
@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)
|
||||
public_send(log_level) do
|
||||
"request: #{env.method.upcase} #{apply_filters(env.url.to_s)}"
|
||||
end
|
||||
|
||||
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)
|
||||
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)
|
||||
end
|
||||
|
||||
def exception(exc)
|
||||
return unless log_errors?
|
||||
|
||||
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)
|
||||
|
||||
log_body('error', exc.response_body)
|
||||
end
|
||||
|
||||
def filter(filter_word, filter_replacement)
|
||||
@filter.push([filter_word, filter_replacement])
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def dump_headers(headers)
|
||||
return if headers.nil?
|
||||
|
||||
headers.map { |k, v| "#{k}: #{v.inspect}" }.join("\n")
|
||||
end
|
||||
|
||||
def dump_body(body)
|
||||
if body.respond_to?(:to_str)
|
||||
body.to_str
|
||||
else
|
||||
pretty_inspect(body)
|
||||
end
|
||||
end
|
||||
|
||||
def pretty_inspect(body)
|
||||
body.pretty_inspect
|
||||
end
|
||||
|
||||
def log_headers?(type)
|
||||
case @options[:headers]
|
||||
when Hash
|
||||
@options[:headers][type]
|
||||
else
|
||||
@options[:headers]
|
||||
end
|
||||
end
|
||||
|
||||
def log_body?(type)
|
||||
case @options[:bodies]
|
||||
when Hash
|
||||
@options[:bodies][type]
|
||||
else
|
||||
@options[:bodies]
|
||||
end
|
||||
end
|
||||
|
||||
def log_errors?
|
||||
@options[:errors]
|
||||
end
|
||||
|
||||
def apply_filters(output)
|
||||
@filter.each do |pattern, replacement|
|
||||
output = output.to_s.gsub(pattern, replacement)
|
||||
end
|
||||
output
|
||||
end
|
||||
|
||||
def log_level
|
||||
@options[:log_level]
|
||||
end
|
||||
|
||||
def log_headers(type, headers)
|
||||
public_send(log_level) { "#{type}: #{apply_filters(dump_headers(headers))}" }
|
||||
end
|
||||
|
||||
def log_body(type, body)
|
||||
public_send(log_level) { "#{type}: #{apply_filters(dump_body(body))}" }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
6
lib/faraday/methods.rb
Normal file
@ -0,0 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Faraday
|
||||
METHODS_WITH_QUERY = %w[get head delete trace].freeze
|
||||
METHODS_WITH_BODY = %w[post put patch].freeze
|
||||
end
|
@ -1,37 +1,72 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'monitor'
|
||||
|
||||
module Faraday
|
||||
# Middleware is the basic base class of any Faraday middleware.
|
||||
class Middleware
|
||||
extend MiddlewareRegistry
|
||||
|
||||
class << self
|
||||
attr_accessor :load_error
|
||||
private :load_error=
|
||||
end
|
||||
attr_reader :app, :options
|
||||
|
||||
self.load_error = nil
|
||||
DEFAULT_OPTIONS = {}.freeze
|
||||
LOCK = Mutex.new
|
||||
|
||||
# Executes a block which should try to require and reference dependent libraries
|
||||
def self.dependency(lib = nil)
|
||||
lib ? require(lib) : yield
|
||||
rescue LoadError, NameError => error
|
||||
self.load_error = error
|
||||
end
|
||||
|
||||
def self.new(*)
|
||||
raise "missing dependency for #{self}: #{load_error.message}" unless loaded?
|
||||
super
|
||||
end
|
||||
|
||||
def self.loaded?
|
||||
load_error.nil?
|
||||
end
|
||||
|
||||
def self.inherited(subclass)
|
||||
super
|
||||
subclass.send(:load_error=, self.load_error)
|
||||
end
|
||||
|
||||
def initialize(app = nil)
|
||||
def initialize(app = nil, options = {})
|
||||
@app = app
|
||||
@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)
|
||||
on_request(env) if respond_to?(:on_request)
|
||||
app.call(env).on_complete do |environment|
|
||||
on_complete(environment) if respond_to?(:on_complete)
|
||||
end
|
||||
rescue StandardError => e
|
||||
on_error(e) if respond_to?(:on_error)
|
||||
raise
|
||||
end
|
||||
|
||||
def close
|
||||
if app.respond_to?(:close)
|
||||
app.close
|
||||
else
|
||||
warn "#{app} does not implement \#close!"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
83
lib/faraday/middleware_registry.rb
Normal file
@ -0,0 +1,83 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'monitor'
|
||||
|
||||
module Faraday
|
||||
# Adds the ability for other modules to register and lookup
|
||||
# middleware classes.
|
||||
module MiddlewareRegistry
|
||||
def registered_middleware
|
||||
@registered_middleware ||= {}
|
||||
end
|
||||
|
||||
# Register middleware class(es) on the current module.
|
||||
#
|
||||
# @param mappings [Hash] Middleware mappings from a lookup symbol to a middleware class.
|
||||
# @return [void]
|
||||
#
|
||||
# @example Lookup by a constant
|
||||
#
|
||||
# module Faraday
|
||||
# class Whatever < Middleware
|
||||
# # Middleware looked up by :foo returns Faraday::Whatever::Foo.
|
||||
# register_middleware(foo: Whatever)
|
||||
# end
|
||||
# end
|
||||
def register_middleware(**mappings)
|
||||
middleware_mutex do
|
||||
registered_middleware.update(mappings)
|
||||
end
|
||||
end
|
||||
|
||||
# Unregister a previously registered middleware class.
|
||||
#
|
||||
# @param key [Symbol] key for the registered middleware.
|
||||
def unregister_middleware(key)
|
||||
registered_middleware.delete(key)
|
||||
end
|
||||
|
||||
# Lookup middleware class with a registered Symbol shortcut.
|
||||
#
|
||||
# @param key [Symbol] key for the registered middleware.
|
||||
# @return [Class] a middleware Class.
|
||||
# @raise [Faraday::Error] if given key is not registered
|
||||
#
|
||||
# @example
|
||||
#
|
||||
# module Faraday
|
||||
# class Whatever < Middleware
|
||||
# register_middleware(foo: Whatever)
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Faraday::Middleware.lookup_middleware(:foo)
|
||||
# # => Faraday::Whatever
|
||||
def lookup_middleware(key)
|
||||
load_middleware(key) ||
|
||||
raise(Faraday::Error, "#{key.inspect} is not registered on #{self}")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def middleware_mutex(&block)
|
||||
@middleware_mutex ||= Monitor.new
|
||||
@middleware_mutex.synchronize(&block)
|
||||
end
|
||||
|
||||
def load_middleware(key)
|
||||
value = registered_middleware[key]
|
||||
case value
|
||||
when Module
|
||||
value
|
||||
when Symbol, String
|
||||
middleware_mutex do
|
||||
@registered_middleware[key] = const_get(value)
|
||||
end
|
||||
when Proc
|
||||
middleware_mutex do
|
||||
@registered_middleware[key] = value.call
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Faraday
|
||||
# Subclasses Struct with some special helpers for converting from a Hash to
|
||||
# a Struct.
|
||||
@ -10,6 +12,7 @@ module Faraday
|
||||
# Public
|
||||
def each
|
||||
return to_enum(:each) unless block_given?
|
||||
|
||||
members.each do |key|
|
||||
yield(key.to_sym, send(key))
|
||||
end
|
||||
@ -27,7 +30,7 @@ module Faraday
|
||||
new_value = value
|
||||
end
|
||||
|
||||
self.send("#{key}=", new_value) unless new_value.nil?
|
||||
send(:"#{key}=", new_value) unless new_value.nil?
|
||||
end
|
||||
self
|
||||
end
|
||||
@ -35,7 +38,7 @@ module Faraday
|
||||
# Public
|
||||
def delete(key)
|
||||
value = send(key)
|
||||
send("#{key}=", nil)
|
||||
send(:"#{key}=", nil)
|
||||
value
|
||||
end
|
||||
|
||||
@ -47,10 +50,14 @@ module Faraday
|
||||
# Public
|
||||
def merge!(other)
|
||||
other.each do |key, other_value|
|
||||
self_value = self.send(key)
|
||||
self_value = send(key)
|
||||
sub_options = self.class.options_for(key)
|
||||
new_value = (self_value && sub_options && other_value) ? self_value.merge(other_value) : other_value
|
||||
self.send("#{key}=", new_value) unless new_value.nil?
|
||||
new_value = if self_value && sub_options && other_value
|
||||
self_value.merge(other_value)
|
||||
else
|
||||
other_value
|
||||
end
|
||||
send(:"#{key}=", new_value) unless new_value.nil?
|
||||
end
|
||||
self
|
||||
end
|
||||
@ -69,10 +76,10 @@ module Faraday
|
||||
def fetch(key, *args)
|
||||
unless symbolized_key_set.include?(key.to_sym)
|
||||
key_setter = "#{key}="
|
||||
if args.size > 0
|
||||
if !args.empty?
|
||||
send(key_setter, args.first)
|
||||
elsif block_given?
|
||||
send(key_setter, Proc.new.call(key))
|
||||
send(key_setter, yield(key))
|
||||
else
|
||||
raise self.class.fetch_error_class, "key not found: #{key.inspect}"
|
||||
end
|
||||
@ -96,11 +103,10 @@ module Faraday
|
||||
end
|
||||
|
||||
# Public
|
||||
def each_key
|
||||
return to_enum(:each_key) unless block_given?
|
||||
keys.each do |key|
|
||||
yield(key)
|
||||
end
|
||||
def each_key(&block)
|
||||
return to_enum(:each_key) unless block
|
||||
|
||||
keys.each(&block)
|
||||
end
|
||||
|
||||
# Public
|
||||
@ -111,11 +117,10 @@ module Faraday
|
||||
alias has_key? key?
|
||||
|
||||
# Public
|
||||
def each_value
|
||||
return to_enum(:each_value) unless block_given?
|
||||
values.each do |value|
|
||||
yield(value)
|
||||
end
|
||||
def each_value(&block)
|
||||
return to_enum(:each_value) unless block
|
||||
|
||||
values.each(&block)
|
||||
end
|
||||
|
||||
# Public
|
||||
@ -142,9 +147,9 @@ module Faraday
|
||||
value = send(member)
|
||||
values << "#{member}=#{value.inspect}" if value
|
||||
end
|
||||
values = values.empty? ? ' (empty)' : (' ' << values.join(", "))
|
||||
values = values.empty? ? '(empty)' : values.join(', ')
|
||||
|
||||
%(#<#{self.class}#{values}>)
|
||||
%(#<#{self.class} #{values}>)
|
||||
end
|
||||
|
||||
# Internal
|
||||
@ -162,9 +167,14 @@ module Faraday
|
||||
@attribute_options ||= {}
|
||||
end
|
||||
|
||||
def self.memoized(key)
|
||||
memoized_attributes[key.to_sym] = Proc.new
|
||||
def self.memoized(key, &block)
|
||||
unless block
|
||||
raise ArgumentError, '#memoized must be called with a block'
|
||||
end
|
||||
|
||||
memoized_attributes[key.to_sym] = block
|
||||
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
||||
remove_method(key) if method_defined?(key, false)
|
||||
def #{key}() self[:#{key}]; end
|
||||
RUBY
|
||||
end
|
||||
@ -175,7 +185,7 @@ module Faraday
|
||||
|
||||
def [](key)
|
||||
key = key.to_sym
|
||||
if method = self.class.memoized_attributes[key]
|
||||
if (method = self.class.memoized_attributes[key])
|
||||
super(key) || (self[key] = instance_eval(&method))
|
||||
else
|
||||
super
|
||||
@ -183,7 +193,7 @@ module Faraday
|
||||
end
|
||||
|
||||
def symbolized_key_set
|
||||
@symbolized_key_set ||= Set.new(keys.map { |k| k.to_sym })
|
||||
@symbolized_key_set ||= Set.new(keys.map(&:to_sym))
|
||||
end
|
||||
|
||||
def self.inherited(subclass)
|
||||
@ -194,180 +204,16 @@ module Faraday
|
||||
|
||||
def self.fetch_error_class
|
||||
@fetch_error_class ||= if Object.const_defined?(:KeyError)
|
||||
::KeyError
|
||||
else
|
||||
::IndexError
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class RequestOptions < Options.new(:params_encoder, :proxy, :bind,
|
||||
:timeout, :open_timeout, :write_timeout, :boundary, :oauth, :context)
|
||||
|
||||
def []=(key, value)
|
||||
if key && key.to_sym == :proxy
|
||||
super(key, value ? ProxyOptions.from(value) : nil)
|
||||
else
|
||||
super(key, value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class SSLOptions < Options.new(:verify, :ca_file, :ca_path, :verify_mode,
|
||||
:cert_store, :client_cert, :client_key, :certificate, :private_key, :verify_depth,
|
||||
:version, :min_version, :max_version)
|
||||
|
||||
def verify?
|
||||
verify != false
|
||||
end
|
||||
|
||||
def disable?
|
||||
!verify?
|
||||
end
|
||||
end
|
||||
|
||||
class ProxyOptions < Options.new(:uri, :user, :password)
|
||||
extend Forwardable
|
||||
def_delegators :uri, :scheme, :scheme=, :host, :host=, :port, :port=, :path, :path=
|
||||
|
||||
def self.from(value)
|
||||
case value
|
||||
when String
|
||||
value = {:uri => Utils.URI(value)}
|
||||
when URI
|
||||
value = {:uri => value}
|
||||
when Hash, Options
|
||||
if uri = value.delete(:uri)
|
||||
value[:uri] = Utils.URI(uri)
|
||||
end
|
||||
end
|
||||
super(value)
|
||||
end
|
||||
|
||||
memoized(:user) { uri && uri.user && Utils.unescape(uri.user) }
|
||||
memoized(:password) { uri && uri.password && Utils.unescape(uri.password) }
|
||||
end
|
||||
|
||||
class ConnectionOptions < Options.new(:request, :proxy, :ssl, :builder, :url,
|
||||
:parallel_manager, :params, :headers, :builder_class)
|
||||
|
||||
options :request => RequestOptions, :ssl => SSLOptions
|
||||
|
||||
memoized(:request) { self.class.options_for(:request).new }
|
||||
|
||||
memoized(:ssl) { self.class.options_for(:ssl).new }
|
||||
|
||||
memoized(:builder_class) { RackBuilder }
|
||||
|
||||
def new_builder(block)
|
||||
builder_class.new(&block)
|
||||
end
|
||||
end
|
||||
|
||||
class Env < Options.new(:method, :body, :url, :request, :request_headers,
|
||||
:ssl, :parallel_manager, :params, :response, :response_headers, :status,
|
||||
:reason_phrase)
|
||||
|
||||
ContentLength = 'Content-Length'.freeze
|
||||
StatusesWithoutBody = Set.new [204, 304]
|
||||
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.
|
||||
MethodsWithBodies = Set.new [:post, :put, :patch, :options]
|
||||
|
||||
options :request => RequestOptions,
|
||||
:request_headers => Utils::Headers, :response_headers => Utils::Headers
|
||||
|
||||
extend Forwardable
|
||||
|
||||
def_delegators :request, :params_encoder
|
||||
|
||||
# Public
|
||||
def self.from(value)
|
||||
env = super(value)
|
||||
if value.respond_to?(:custom_members)
|
||||
env.custom_members.update(value.custom_members)
|
||||
end
|
||||
env
|
||||
end
|
||||
|
||||
# Public
|
||||
def [](key)
|
||||
if in_member_set?(key)
|
||||
super(key)
|
||||
else
|
||||
custom_members[key]
|
||||
end
|
||||
end
|
||||
|
||||
# Public
|
||||
def []=(key, value)
|
||||
if in_member_set?(key)
|
||||
super(key, value)
|
||||
else
|
||||
custom_members[key] = value
|
||||
end
|
||||
end
|
||||
|
||||
# Public
|
||||
def success?
|
||||
SuccessfulStatuses.include?(status)
|
||||
end
|
||||
|
||||
# Public
|
||||
def needs_body?
|
||||
!body && MethodsWithBodies.include?(method)
|
||||
end
|
||||
|
||||
# Public
|
||||
def clear_body
|
||||
request_headers[ContentLength] = '0'
|
||||
self.body = ''
|
||||
end
|
||||
|
||||
# Public
|
||||
def parse_body?
|
||||
!StatusesWithoutBody.include?(status)
|
||||
end
|
||||
|
||||
# Public
|
||||
def parallel?
|
||||
!!parallel_manager
|
||||
end
|
||||
|
||||
def inspect
|
||||
attrs = [nil]
|
||||
members.each do |mem|
|
||||
if value = send(mem)
|
||||
attrs << "@#{mem}=#{value.inspect}"
|
||||
end
|
||||
end
|
||||
if !custom_members.empty?
|
||||
attrs << "@custom=#{custom_members.inspect}"
|
||||
end
|
||||
%(#<#{self.class}#{attrs.join(" ")}>)
|
||||
end
|
||||
|
||||
# Internal
|
||||
def custom_members
|
||||
@custom_members ||= {}
|
||||
end
|
||||
|
||||
# Internal
|
||||
if members.first.is_a?(Symbol)
|
||||
def in_member_set?(key)
|
||||
self.class.member_set.include?(key.to_sym)
|
||||
end
|
||||
else
|
||||
def in_member_set?(key)
|
||||
self.class.member_set.include?(key.to_s)
|
||||
end
|
||||
end
|
||||
|
||||
# Internal
|
||||
def self.member_set
|
||||
@member_set ||= Set.new(members)
|
||||
::KeyError
|
||||
else
|
||||
::IndexError
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require 'faraday/options/request_options'
|
||||
require 'faraday/options/ssl_options'
|
||||
require 'faraday/options/proxy_options'
|
||||
require 'faraday/options/connection_options'
|
||||
require 'faraday/options/env'
|
||||
|
23
lib/faraday/options/connection_options.rb
Normal file
@ -0,0 +1,23 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Faraday
|
||||
# @!parse
|
||||
# # ConnectionOptions contains the configurable properties for a Faraday
|
||||
# # connection object.
|
||||
# class ConnectionOptions < Options; end
|
||||
ConnectionOptions = Options.new(:request, :proxy, :ssl, :builder, :url,
|
||||
:parallel_manager, :params, :headers,
|
||||
:builder_class) do
|
||||
options request: RequestOptions, ssl: SSLOptions
|
||||
|
||||
memoized(:request) { self.class.options_for(:request).new }
|
||||
|
||||
memoized(:ssl) { self.class.options_for(:ssl).new }
|
||||
|
||||
memoized(:builder_class) { RackBuilder }
|
||||
|
||||
def new_builder(block)
|
||||
builder_class.new(&block)
|
||||
end
|
||||
end
|
||||
end
|
204
lib/faraday/options/env.rb
Normal file
@ -0,0 +1,204 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Faraday
|
||||
# @!parse
|
||||
# # @!attribute method
|
||||
# # @return [Symbol] HTTP method (`:get`, `:post`)
|
||||
# #
|
||||
# # @!attribute body
|
||||
# # @return [String] The request body that will eventually be converted to a
|
||||
# # string.
|
||||
# #
|
||||
# # @!attribute url
|
||||
# # @return [URI] URI instance for the current request.
|
||||
# #
|
||||
# # @!attribute request
|
||||
# # @return [Hash] options for configuring the request.
|
||||
# # Options for configuring the request.
|
||||
# #
|
||||
# # - `:timeout` - time limit for the entire request (Integer in
|
||||
# # seconds)
|
||||
# # - `:open_timeout` - time limit for just the connection phase (e.g.
|
||||
# # handshake) (Integer in seconds)
|
||||
# # - `:read_timeout` - time limit for the first response byte received from
|
||||
# # the server (Integer in seconds)
|
||||
# # - `:write_timeout` - time limit for the client to send the request to the
|
||||
# # server (Integer in seconds)
|
||||
# # - `:on_data` - Proc for streaming
|
||||
# # - `:proxy` - Hash of proxy options
|
||||
# # - `:uri` - Proxy server URI
|
||||
# # - `:user` - Proxy server username
|
||||
# # - `:password` - Proxy server password
|
||||
# #
|
||||
# # @!attribute request_headers
|
||||
# # @return [Hash] HTTP Headers to be sent to the server.
|
||||
# #
|
||||
# # @!attribute ssl
|
||||
# # @return [Hash] options for configuring SSL requests
|
||||
# #
|
||||
# # @!attribute parallel_manager
|
||||
# # @return [Object] sent if the connection is in parallel mode
|
||||
# #
|
||||
# # @!attribute params
|
||||
# # @return [Hash]
|
||||
# #
|
||||
# # @!attribute response
|
||||
# # @return [Response]
|
||||
# #
|
||||
# # @!attribute response_headers
|
||||
# # @return [Hash] HTTP headers from the server
|
||||
# #
|
||||
# # @!attribute status
|
||||
# # @return [Integer] HTTP response status code
|
||||
# #
|
||||
# # @!attribute reason_phrase
|
||||
# # @return [String]
|
||||
# class Env < Options; end
|
||||
Env = Options.new(:method, :request_body, :url, :request,
|
||||
:request_headers, :ssl, :parallel_manager, :params,
|
||||
:response, :response_headers, :status,
|
||||
:reason_phrase, :response_body) do
|
||||
const_set(:ContentLength, 'Content-Length')
|
||||
const_set(:StatusesWithoutBody, Set.new([204, 304]))
|
||||
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.
|
||||
const_set(:MethodsWithBodies, Set.new(Faraday::METHODS_WITH_BODY.map(&:to_sym)))
|
||||
|
||||
options request: RequestOptions,
|
||||
request_headers: Utils::Headers, response_headers: Utils::Headers
|
||||
|
||||
extend Forwardable
|
||||
|
||||
def_delegators :request, :params_encoder
|
||||
|
||||
# Build a new Env from given value. Respects and updates `custom_members`.
|
||||
#
|
||||
# @param value [Object] a value fitting Option.from(v).
|
||||
# @return [Env] from given value
|
||||
def self.from(value)
|
||||
env = super(value)
|
||||
if value.respond_to?(:custom_members)
|
||||
env.custom_members.update(value.custom_members)
|
||||
end
|
||||
env
|
||||
end
|
||||
|
||||
# @param key [Object]
|
||||
def [](key)
|
||||
return self[current_body] if key == :body
|
||||
|
||||
if in_member_set?(key)
|
||||
super(key)
|
||||
else
|
||||
custom_members[key]
|
||||
end
|
||||
end
|
||||
|
||||
# @param key [Object]
|
||||
# @param value [Object]
|
||||
def []=(key, value)
|
||||
if key == :body
|
||||
super(current_body, value)
|
||||
return
|
||||
end
|
||||
|
||||
if in_member_set?(key)
|
||||
super(key, value)
|
||||
else
|
||||
custom_members[key] = value
|
||||
end
|
||||
end
|
||||
|
||||
def current_body
|
||||
!!status ? :response_body : :request_body
|
||||
end
|
||||
|
||||
def body
|
||||
self[:body]
|
||||
end
|
||||
|
||||
def body=(value)
|
||||
self[:body] = value
|
||||
end
|
||||
|
||||
# @return [Boolean] true if status is in the set of {SuccessfulStatuses}.
|
||||
def success?
|
||||
Env::SuccessfulStatuses.include?(status)
|
||||
end
|
||||
|
||||
# @return [Boolean] true if there's no body yet, and the method is in the
|
||||
# set of {Env::MethodsWithBodies}.
|
||||
def needs_body?
|
||||
!body && Env::MethodsWithBodies.include?(method)
|
||||
end
|
||||
|
||||
# Sets content length to zero and the body to the empty string.
|
||||
def clear_body
|
||||
request_headers[Env::ContentLength] = '0'
|
||||
self.body = +''
|
||||
end
|
||||
|
||||
# @return [Boolean] true if the status isn't in the set of
|
||||
# {Env::StatusesWithoutBody}.
|
||||
def parse_body?
|
||||
!Env::StatusesWithoutBody.include?(status)
|
||||
end
|
||||
|
||||
# @return [Boolean] true if there is a parallel_manager
|
||||
def parallel?
|
||||
!!parallel_manager
|
||||
end
|
||||
|
||||
def inspect
|
||||
attrs = [nil]
|
||||
members.each do |mem|
|
||||
if (value = send(mem))
|
||||
attrs << "@#{mem}=#{value.inspect}"
|
||||
end
|
||||
end
|
||||
attrs << "@custom=#{custom_members.inspect}" unless custom_members.empty?
|
||||
%(#<#{self.class}#{attrs.join(' ')}>)
|
||||
end
|
||||
|
||||
def stream_response?
|
||||
request.stream_response?
|
||||
end
|
||||
|
||||
def stream_response(&block)
|
||||
size = 0
|
||||
yielded = false
|
||||
block_result = block.call do |chunk|
|
||||
if chunk.bytesize.positive? || size.positive?
|
||||
yielded = true
|
||||
size += chunk.bytesize
|
||||
request.on_data.call(chunk, size, self)
|
||||
end
|
||||
end
|
||||
request.on_data.call(+'', 0, self) unless yielded
|
||||
block_result
|
||||
end
|
||||
|
||||
# @private
|
||||
def custom_members
|
||||
@custom_members ||= {}
|
||||
end
|
||||
|
||||
# @private
|
||||
if members.first.is_a?(Symbol)
|
||||
def in_member_set?(key)
|
||||
self.class.member_set.include?(key.to_sym)
|
||||
end
|
||||
else
|
||||
def in_member_set?(key)
|
||||
self.class.member_set.include?(key.to_s)
|
||||
end
|
||||
end
|
||||
|
||||
# @private
|
||||
def self.member_set
|
||||
@member_set ||= Set.new(members)
|
||||
end
|
||||
end
|
||||
end
|
38
lib/faraday/options/proxy_options.rb
Normal file
@ -0,0 +1,38 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Faraday
|
||||
# @!parse
|
||||
# # ProxyOptions contains the configurable properties for the proxy
|
||||
# # configuration used when making an HTTP request.
|
||||
# class ProxyOptions < Options; end
|
||||
ProxyOptions = Options.new(:uri, :user, :password) do
|
||||
extend Forwardable
|
||||
def_delegators :uri, :scheme, :scheme=, :host, :host=, :port, :port=,
|
||||
:path, :path=
|
||||
|
||||
def self.from(value)
|
||||
case value
|
||||
when ''
|
||||
value = nil
|
||||
when String
|
||||
# URIs without a scheme should default to http (like 'example:123').
|
||||
# This fixes #1282 and prevents a silent failure in some adapters.
|
||||
value = "http://#{value}" unless value.include?('://')
|
||||
value = { uri: Utils.URI(value) }
|
||||
when URI
|
||||
value = { uri: value }
|
||||
when Hash, Options
|
||||
if value[:uri]
|
||||
value = value.dup.tap do |duped|
|
||||
duped[:uri] = Utils.URI(duped[:uri])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
super(value)
|
||||
end
|
||||
|
||||
memoized(:user) { uri&.user && Utils.unescape(uri.user) }
|
||||
memoized(:password) { uri&.password && Utils.unescape(uri.password) }
|
||||
end
|
||||
end
|
23
lib/faraday/options/request_options.rb
Normal file
@ -0,0 +1,23 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Faraday
|
||||
# @!parse
|
||||
# # RequestOptions contains the configurable properties for a Faraday request.
|
||||
# class RequestOptions < Options; end
|
||||
RequestOptions = Options.new(:params_encoder, :proxy, :bind,
|
||||
:timeout, :open_timeout, :read_timeout,
|
||||
:write_timeout, :boundary, :oauth,
|
||||
:context, :on_data) do
|
||||
def []=(key, value)
|
||||
if key && key.to_sym == :proxy
|
||||
super(key, value ? ProxyOptions.from(value) : nil)
|
||||
else
|
||||
super(key, value)
|
||||
end
|
||||
end
|
||||
|
||||
def stream_response?
|
||||
on_data.is_a?(Proc)
|
||||
end
|
||||
end
|
||||
end
|
76
lib/faraday/options/ssl_options.rb
Normal file
@ -0,0 +1,76 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Faraday
|
||||
# @!parse
|
||||
# # SSL-related options.
|
||||
# #
|
||||
# # @!attribute verify
|
||||
# # @return [Boolean] whether to verify SSL certificates or not
|
||||
# #
|
||||
# # @!attribute verify_hostname
|
||||
# # @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
|
||||
# #
|
||||
# # @!attribute ca_path
|
||||
# # @return [String] CA path
|
||||
# #
|
||||
# # @!attribute verify_mode
|
||||
# # @return [Integer] Any `OpenSSL::SSL::` constant (see https://ruby-doc.org/stdlib-2.5.1/libdoc/openssl/rdoc/OpenSSL/SSL.html)
|
||||
# #
|
||||
# # @!attribute cert_store
|
||||
# # @return [OpenSSL::X509::Store] certificate store
|
||||
# #
|
||||
# # @!attribute client_cert
|
||||
# # @return [String, OpenSSL::X509::Certificate] client certificate
|
||||
# #
|
||||
# # @!attribute client_key
|
||||
# # @return [String, OpenSSL::PKey::RSA, OpenSSL::PKey::DSA] client key
|
||||
# #
|
||||
# # @!attribute certificate
|
||||
# # @return [OpenSSL::X509::Certificate] certificate (Excon only)
|
||||
# #
|
||||
# # @!attribute private_key
|
||||
# # @return [OpenSSL::PKey::RSA, OpenSSL::PKey::DSA] private key (Excon only)
|
||||
# #
|
||||
# # @!attribute verify_depth
|
||||
# # @return [Integer] maximum depth for the certificate chain verification
|
||||
# #
|
||||
# # @!attribute version
|
||||
# # @return [String, Symbol] SSL version (see https://ruby-doc.org/stdlib-2.5.1/libdoc/openssl/rdoc/OpenSSL/SSL/SSLContext.html#method-i-ssl_version-3D)
|
||||
# #
|
||||
# # @!attribute min_version
|
||||
# # @return [String, Symbol] minimum SSL version (see https://ruby-doc.org/stdlib-2.5.1/libdoc/openssl/rdoc/OpenSSL/SSL/SSLContext.html#method-i-min_version-3D)
|
||||
# #
|
||||
# # @!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, :hostname,
|
||||
:ca_file, :ca_path, :verify_mode,
|
||||
:cert_store, :client_cert, :client_key,
|
||||
:certificate, :private_key, :verify_depth,
|
||||
:version, :min_version, :max_version, :ciphers) do
|
||||
# @return [Boolean] true if should verify
|
||||
def verify?
|
||||
verify != false
|
||||
end
|
||||
|
||||
# @return [Boolean] true if should not verify
|
||||
def disable?
|
||||
!verify?
|
||||
end
|
||||
|
||||
# @return [Boolean] true if should verify_hostname
|
||||
def verify_hostname?
|
||||
verify_hostname != false
|
||||
end
|
||||
end
|
||||
end
|
@ -1,198 +1,5 @@
|
||||
require "forwardable"
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Faraday
|
||||
module NestedParamsEncoder
|
||||
class << self
|
||||
extend Forwardable
|
||||
def_delegators :'Faraday::Utils', :escape, :unescape
|
||||
end
|
||||
|
||||
def self.encode(params)
|
||||
return nil if params == nil
|
||||
|
||||
if !params.is_a?(Array)
|
||||
if !params.respond_to?(:to_hash)
|
||||
raise TypeError,
|
||||
"Can't convert #{params.class} into Hash."
|
||||
end
|
||||
params = params.to_hash
|
||||
params = params.map do |key, value|
|
||||
key = key.to_s if key.kind_of?(Symbol)
|
||||
[key, value]
|
||||
end
|
||||
# Useful default for OAuth and caching.
|
||||
# Only to be used for non-Array inputs. Arrays should preserve order.
|
||||
params.sort!
|
||||
end
|
||||
|
||||
# Helper lambda
|
||||
to_query = lambda do |parent, value|
|
||||
if value.is_a?(Hash)
|
||||
value = value.map do |key, val|
|
||||
key = escape(key)
|
||||
[key, val]
|
||||
end
|
||||
value.sort!
|
||||
buffer = ""
|
||||
value.each do |key, val|
|
||||
new_parent = "#{parent}%5B#{key}%5D"
|
||||
buffer << "#{to_query.call(new_parent, val)}&"
|
||||
end
|
||||
return buffer.chop
|
||||
elsif value.is_a?(Array)
|
||||
new_parent = "#{parent}%5B%5D"
|
||||
return new_parent if value.empty?
|
||||
buffer = ""
|
||||
value.each_with_index do |val, i|
|
||||
buffer << "#{to_query.call(new_parent, val)}&"
|
||||
end
|
||||
return buffer.chop
|
||||
elsif value.nil?
|
||||
return parent
|
||||
else
|
||||
encoded_value = escape(value)
|
||||
return "#{parent}=#{encoded_value}"
|
||||
end
|
||||
end
|
||||
|
||||
# The params have form [['key1', 'value1'], ['key2', 'value2']].
|
||||
buffer = ''
|
||||
params.each do |parent, value|
|
||||
encoded_parent = escape(parent)
|
||||
buffer << "#{to_query.call(encoded_parent, value)}&"
|
||||
end
|
||||
return buffer.chop
|
||||
end
|
||||
|
||||
def self.decode(query)
|
||||
return nil if query == nil
|
||||
|
||||
params = {}
|
||||
query.split("&").each do |pair|
|
||||
next if pair.empty?
|
||||
key, value = pair.split("=", 2)
|
||||
key = unescape(key)
|
||||
value = unescape(value.gsub(/\+/, ' ')) if value
|
||||
|
||||
subkeys = key.scan(/[^\[\]]+(?:\]?\[\])?/)
|
||||
context = params
|
||||
subkeys.each_with_index do |subkey, i|
|
||||
is_array = subkey =~ /[\[\]]+\Z/
|
||||
subkey = $` if is_array
|
||||
last_subkey = i == subkeys.length - 1
|
||||
|
||||
if !last_subkey || is_array
|
||||
value_type = is_array ? Array : Hash
|
||||
if context[subkey] && !context[subkey].is_a?(value_type)
|
||||
raise TypeError, "expected %s (got %s) for param `%s'" % [
|
||||
value_type.name,
|
||||
context[subkey].class.name,
|
||||
subkey
|
||||
]
|
||||
end
|
||||
context = (context[subkey] ||= value_type.new)
|
||||
end
|
||||
|
||||
if context.is_a?(Array) && !is_array
|
||||
if !context.last.is_a?(Hash) || context.last.has_key?(subkey)
|
||||
context << {}
|
||||
end
|
||||
context = context.last
|
||||
end
|
||||
|
||||
if last_subkey
|
||||
if is_array
|
||||
context << value
|
||||
else
|
||||
context[subkey] = value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
dehash(params, 0)
|
||||
end
|
||||
|
||||
# Internal: convert a nested hash with purely numeric keys into an array.
|
||||
# FIXME: this is not compatible with Rack::Utils.parse_nested_query
|
||||
def self.dehash(hash, depth)
|
||||
hash.each do |key, value|
|
||||
hash[key] = dehash(value, depth + 1) if value.kind_of?(Hash)
|
||||
end
|
||||
|
||||
if depth > 0 && !hash.empty? && hash.keys.all? { |k| k =~ /^\d+$/ }
|
||||
hash.keys.sort.inject([]) { |all, key| all << hash[key] }
|
||||
else
|
||||
hash
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module FlatParamsEncoder
|
||||
class << self
|
||||
extend Forwardable
|
||||
def_delegators :'Faraday::Utils', :escape, :unescape
|
||||
end
|
||||
|
||||
def self.encode(params)
|
||||
return nil if params == nil
|
||||
|
||||
if !params.is_a?(Array)
|
||||
if !params.respond_to?(:to_hash)
|
||||
raise TypeError,
|
||||
"Can't convert #{params.class} into Hash."
|
||||
end
|
||||
params = params.to_hash
|
||||
params = params.map do |key, value|
|
||||
key = key.to_s if key.kind_of?(Symbol)
|
||||
[key, value]
|
||||
end
|
||||
# Useful default for OAuth and caching.
|
||||
# Only to be used for non-Array inputs. Arrays should preserve order.
|
||||
params.sort!
|
||||
end
|
||||
|
||||
# The params have form [['key1', 'value1'], ['key2', 'value2']].
|
||||
buffer = ''
|
||||
params.each do |key, value|
|
||||
encoded_key = escape(key)
|
||||
value = value.to_s if value == true || value == false
|
||||
if value == nil
|
||||
buffer << "#{encoded_key}&"
|
||||
elsif value.kind_of?(Array)
|
||||
value.each do |sub_value|
|
||||
encoded_value = escape(sub_value)
|
||||
buffer << "#{encoded_key}=#{encoded_value}&"
|
||||
end
|
||||
else
|
||||
encoded_value = escape(value)
|
||||
buffer << "#{encoded_key}=#{encoded_value}&"
|
||||
end
|
||||
end
|
||||
return buffer.chop
|
||||
end
|
||||
|
||||
def self.decode(query)
|
||||
empty_accumulator = {}
|
||||
return nil if query == nil
|
||||
split_query = (query.split('&').map do |pair|
|
||||
pair.split('=', 2) if pair && !pair.empty?
|
||||
end).compact
|
||||
return split_query.inject(empty_accumulator.dup) do |accu, pair|
|
||||
pair[0] = unescape(pair[0])
|
||||
pair[1] = true if pair[1].nil?
|
||||
if pair[1].respond_to?(:to_str)
|
||||
pair[1] = unescape(pair[1].to_str.gsub(/\+/, " "))
|
||||
end
|
||||
if accu[pair[0]].kind_of?(Array)
|
||||
accu[pair[0]] << pair[1]
|
||||
elsif accu[pair[0]]
|
||||
accu[pair[0]] = [accu[pair[0]], pair[1]]
|
||||
else
|
||||
accu[pair[0]] = pair[1]
|
||||
end
|
||||
accu
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
require 'forwardable'
|
||||
require 'faraday/encoders/nested_params_encoder'
|
||||
require 'faraday/encoders/flat_params_encoder'
|
||||
|
@ -1,12 +1,20 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'faraday/adapter_registry'
|
||||
|
||||
module Faraday
|
||||
# A Builder that processes requests into responses by passing through an inner
|
||||
# middleware stack (heavily inspired by Rack).
|
||||
#
|
||||
# Faraday::Connection.new(:url => 'http://sushi.com') do |builder|
|
||||
# @example
|
||||
# Faraday::Connection.new(url: 'http://httpbingo.org') do |builder|
|
||||
# builder.request :url_encoded # Faraday::Request::UrlEncoded
|
||||
# builder.adapter :net_http # Faraday::Adapter::NetHttp
|
||||
# end
|
||||
class RackBuilder
|
||||
# Used to detect missing arguments
|
||||
NO_ARGUMENT = Object.new
|
||||
|
||||
attr_accessor :handlers
|
||||
|
||||
# Error raised when trying to modify the stack after calling `lock!`
|
||||
@ -15,28 +23,29 @@ module Faraday
|
||||
# borrowed from ActiveSupport::Dependencies::Reference &
|
||||
# ActionDispatch::MiddlewareStack::Middleware
|
||||
class Handler
|
||||
@@constants_mutex = Mutex.new
|
||||
@@constants = Hash.new { |h, k|
|
||||
value = k.respond_to?(:constantize) ? k.constantize : Object.const_get(k)
|
||||
@@constants_mutex.synchronize { h[k] = value }
|
||||
}
|
||||
REGISTRY = Faraday::AdapterRegistry.new
|
||||
|
||||
attr_reader :name
|
||||
|
||||
def initialize(klass, *args, &block)
|
||||
def initialize(klass, *args, **kwargs, &block)
|
||||
@name = klass.to_s
|
||||
if klass.respond_to?(:name)
|
||||
@@constants_mutex.synchronize { @@constants[@name] = klass }
|
||||
end
|
||||
@args, @block = args, block
|
||||
REGISTRY.set(klass) if klass.respond_to?(:name)
|
||||
@args = args
|
||||
@kwargs = kwargs
|
||||
@block = block
|
||||
end
|
||||
|
||||
def klass() @@constants[@name] end
|
||||
def inspect() @name end
|
||||
def klass
|
||||
REGISTRY.get(@name)
|
||||
end
|
||||
|
||||
def inspect
|
||||
@name
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
if other.is_a? Handler
|
||||
self.name == other.name
|
||||
name == other.name
|
||||
elsif other.respond_to? :name
|
||||
klass == other
|
||||
else
|
||||
@ -44,33 +53,34 @@ module Faraday
|
||||
end
|
||||
end
|
||||
|
||||
def build(app)
|
||||
klass.new(app, *@args, &@block)
|
||||
def build(app = nil)
|
||||
klass.new(app, *@args, **@kwargs, &@block)
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(handlers = [])
|
||||
@handlers = handlers
|
||||
if block_given?
|
||||
build(&Proc.new)
|
||||
elsif @handlers.empty?
|
||||
# default stack, if nothing else is configured
|
||||
self.request :url_encoded
|
||||
self.adapter Faraday.default_adapter
|
||||
end
|
||||
def initialize(&block)
|
||||
@adapter = nil
|
||||
@handlers = []
|
||||
build(&block)
|
||||
end
|
||||
|
||||
def build(options = {})
|
||||
def initialize_dup(original)
|
||||
super
|
||||
@adapter = original.adapter
|
||||
@handlers = original.handlers.dup
|
||||
end
|
||||
|
||||
def build
|
||||
raise_if_locked
|
||||
@handlers.clear unless options[:keep]
|
||||
yield(self) if block_given?
|
||||
block_given? ? yield(self) : request(:url_encoded)
|
||||
adapter(Faraday.default_adapter, **Faraday.default_adapter_options) unless @adapter
|
||||
end
|
||||
|
||||
def [](idx)
|
||||
@handlers[idx]
|
||||
end
|
||||
|
||||
# Locks the middleware stack to ensure no further modifications are possible.
|
||||
# Locks the middleware stack to ensure no further modifications are made.
|
||||
def lock!
|
||||
@handlers.freeze
|
||||
end
|
||||
@ -79,50 +89,52 @@ module Faraday
|
||||
@handlers.frozen?
|
||||
end
|
||||
|
||||
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
|
||||
warn_middleware_after_adapter if adapter_set?
|
||||
@handlers << self.class::Handler.new(klass, *args, &block)
|
||||
raise_if_adapter(klass)
|
||||
@handlers << self.class::Handler.new(klass, ...)
|
||||
end
|
||||
end
|
||||
|
||||
def request(key, *args, &block)
|
||||
use_symbol(Faraday::Request, key, *args, &block)
|
||||
def request(key, ...)
|
||||
use_symbol(Faraday::Request, key, ...)
|
||||
end
|
||||
|
||||
def response(key, *args, &block)
|
||||
use_symbol(Faraday::Response, key, *args, &block)
|
||||
def response(...)
|
||||
use_symbol(Faraday::Response, ...)
|
||||
end
|
||||
|
||||
def adapter(key, *args, &block)
|
||||
use_symbol(Faraday::Adapter, key, *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, **kwargs, &block)
|
||||
end
|
||||
|
||||
## methods to push onto the various positions in the stack:
|
||||
|
||||
def insert(index, *args, &block)
|
||||
def insert(index, ...)
|
||||
raise_if_locked
|
||||
index = assert_index(index)
|
||||
warn_middleware_after_adapter if inserting_after_adapter?(index)
|
||||
handler = self.class::Handler.new(*args, &block)
|
||||
handler = self.class::Handler.new(...)
|
||||
@handlers.insert(index, handler)
|
||||
end
|
||||
|
||||
alias_method :insert_before, :insert
|
||||
alias insert_before insert
|
||||
|
||||
def insert_after(index, *args, &block)
|
||||
def insert_after(index, ...)
|
||||
index = assert_index(index)
|
||||
insert(index + 1, *args, &block)
|
||||
insert(index + 1, ...)
|
||||
end
|
||||
|
||||
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)
|
||||
@ -133,13 +145,11 @@ module Faraday
|
||||
# Processes a Request into a Response by passing it through this Builder's
|
||||
# middleware stack.
|
||||
#
|
||||
# connection - Faraday::Connection
|
||||
# request - Faraday::Request
|
||||
# @param connection [Faraday::Connection]
|
||||
# @param request [Faraday::Request]
|
||||
#
|
||||
# Returns a Faraday::Response.
|
||||
# @return [Faraday::Response]
|
||||
def build_response(connection, request)
|
||||
warn 'WARNING: No adapter was configured for this request' unless adapter_set?
|
||||
|
||||
app.call(build_env(connection, request))
|
||||
end
|
||||
|
||||
@ -153,30 +163,27 @@ module Faraday
|
||||
def app
|
||||
@app ||= begin
|
||||
lock!
|
||||
to_app(lambda { |env|
|
||||
response = Response.new
|
||||
env.response = response
|
||||
response.finish(env) unless env.parallel?
|
||||
response
|
||||
})
|
||||
ensure_adapter!
|
||||
to_app
|
||||
end
|
||||
end
|
||||
|
||||
def to_app(inner_app)
|
||||
def to_app
|
||||
# last added handler is the deepest and thus closest to the inner app
|
||||
@handlers.reverse.inject(inner_app) { |app, handler| handler.build(app) }
|
||||
# adapter is always the last one
|
||||
@handlers.reverse.inject(@adapter.build) do |app, handler|
|
||||
handler.build(app)
|
||||
end
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
other.is_a?(self.class) && @handlers == other.handlers
|
||||
end
|
||||
|
||||
def dup
|
||||
self.class.new(@handlers.dup)
|
||||
other.is_a?(self.class) &&
|
||||
@handlers == other.handlers &&
|
||||
@adapter == other.adapter
|
||||
end
|
||||
|
||||
# ENV Keys
|
||||
# :method - a symbolized request method (:get, :post)
|
||||
# :http_method - a symbolized request HTTP method (:get, :post)
|
||||
# :body - the request body that will eventually be converted to a string.
|
||||
# :url - URI instance for the current request.
|
||||
# :status - HTTP response status code
|
||||
@ -192,45 +199,49 @@ module Faraday
|
||||
# :password - Proxy server password
|
||||
# :ssl - Hash of options for configuring SSL requests.
|
||||
def build_env(connection, request)
|
||||
Env.new(request.method, request.body,
|
||||
connection.build_exclusive_url(request.path, request.params, request.options.params_encoder),
|
||||
request.options, request.headers, connection.ssl,
|
||||
connection.parallel_manager)
|
||||
exclusive_url = connection.build_exclusive_url(
|
||||
request.path, request.params,
|
||||
request.options.params_encoder
|
||||
)
|
||||
|
||||
Env.new(request.http_method, request.body, exclusive_url,
|
||||
request.options, request.headers, connection.ssl,
|
||||
connection.parallel_manager)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
LOCK_ERR = "can't modify middleware stack after making a request"
|
||||
MISSING_ADAPTER_ERROR = "An attempt to run a request with a Faraday::Connection without adapter has been made.\n" \
|
||||
"Please set Faraday.default_adapter or provide one when initializing the connection.\n" \
|
||||
'For more info, check https://lostisland.github.io/faraday/usage/.'
|
||||
|
||||
def raise_if_locked
|
||||
raise StackLocked, "can't modify middleware stack after making a request" if locked?
|
||||
raise StackLocked, LOCK_ERR if locked?
|
||||
end
|
||||
|
||||
def warn_middleware_after_adapter
|
||||
warn "WARNING: Unexpected middleware set after the adapter. " \
|
||||
"This won't be supported from Faraday 1.0."
|
||||
def raise_if_adapter(klass)
|
||||
return unless klass <= Faraday::Adapter
|
||||
|
||||
raise 'Adapter should be set using the `adapter` method, not `use`'
|
||||
end
|
||||
|
||||
def ensure_adapter!
|
||||
raise MISSING_ADAPTER_ERROR unless @adapter
|
||||
end
|
||||
|
||||
def adapter_set?
|
||||
@handlers.any? { |handler| is_adapter?(handler) }
|
||||
!@adapter.nil?
|
||||
end
|
||||
|
||||
def inserting_after_adapter?(index)
|
||||
adapter_index = @handlers.find_index { |handler| is_adapter?(handler) }
|
||||
return false if adapter_index.nil?
|
||||
|
||||
index > adapter_index
|
||||
end
|
||||
|
||||
def is_adapter?(handler)
|
||||
handler.klass.ancestors.include? Faraday::Adapter
|
||||
end
|
||||
|
||||
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)
|
||||
idx = index.is_a?(Integer) ? index : @handlers.index(index)
|
||||
raise "No such handler: #{index.inspect}" unless idx
|
||||
|
||||
idx
|
||||
end
|
||||
end
|
||||
|
@ -1,6 +1,9 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Faraday
|
||||
# Used to setup urls, params, headers, and the request body in a sane manner.
|
||||
# Used to setup URLs, params, headers, and the request body in a sane manner.
|
||||
#
|
||||
# @example
|
||||
# @connection.post do |req|
|
||||
# req.url 'http://localhost', 'a' => '1' # 'http://localhost?a=1'
|
||||
# req.headers['b'] = '2' # Header
|
||||
@ -9,45 +12,68 @@ module Faraday
|
||||
# req.body = 'abc'
|
||||
# end
|
||||
#
|
||||
class Request < Struct.new(:method, :path, :params, :headers, :body, :options)
|
||||
# @!attribute http_method
|
||||
# @return [Symbol] the HTTP method of the Request
|
||||
# @!attribute path
|
||||
# @return [URI, String] the path
|
||||
# @!attribute params
|
||||
# @return [Hash] query parameters
|
||||
# @!attribute headers
|
||||
# @return [Faraday::Utils::Headers] headers
|
||||
# @!attribute body
|
||||
# @return [String] body
|
||||
# @!attribute options
|
||||
# @return [RequestOptions] options
|
||||
Request = Struct.new(:http_method, :path, :params, :headers, :body, :options) do
|
||||
extend MiddlewareRegistry
|
||||
|
||||
register_middleware File.expand_path('../request', __FILE__),
|
||||
:url_encoded => [:UrlEncoded, 'url_encoded'],
|
||||
:multipart => [:Multipart, 'multipart'],
|
||||
:retry => [:Retry, 'retry'],
|
||||
:authorization => [:Authorization, 'authorization'],
|
||||
:basic_auth => [:BasicAuthentication, 'basic_authentication'],
|
||||
:token_auth => [:TokenAuthentication, 'token_authentication'],
|
||||
:instrumentation => [:Instrumentation, 'instrumentation']
|
||||
alias_method :member_get, :[]
|
||||
private :member_get
|
||||
alias_method :member_set, :[]=
|
||||
private :member_set
|
||||
|
||||
# @param request_method [String]
|
||||
# @yield [request] for block customization, if block given
|
||||
# @yieldparam request [Request]
|
||||
# @return [Request]
|
||||
def self.create(request_method)
|
||||
new(request_method).tap do |request|
|
||||
yield(request) if block_given?
|
||||
end
|
||||
end
|
||||
|
||||
# Public: Replace params, preserving the existing hash type
|
||||
remove_method :params=
|
||||
# Replace params, preserving the existing hash type.
|
||||
#
|
||||
# @param hash [Hash] new params
|
||||
def params=(hash)
|
||||
if params
|
||||
params.replace hash
|
||||
else
|
||||
super
|
||||
member_set(:params, hash)
|
||||
end
|
||||
end
|
||||
|
||||
# Public: Replace request headers, preserving the existing hash type
|
||||
remove_method :headers=
|
||||
# Replace request headers, preserving the existing hash type.
|
||||
#
|
||||
# @param hash [Hash] new headers
|
||||
def headers=(hash)
|
||||
if headers
|
||||
headers.replace hash
|
||||
else
|
||||
super
|
||||
member_set(:headers, hash)
|
||||
end
|
||||
end
|
||||
|
||||
# Update path and params.
|
||||
#
|
||||
# @param path [URI, String]
|
||||
# @param params [Hash, nil]
|
||||
# @return [void]
|
||||
def url(path, params = nil)
|
||||
if path.respond_to? :query
|
||||
if query = path.query
|
||||
if (query = path.query)
|
||||
path = path.dup
|
||||
path.query = nil
|
||||
end
|
||||
@ -61,54 +87,53 @@ module Faraday
|
||||
self.params.update(params) if params
|
||||
end
|
||||
|
||||
# @param key [Object] key to look up in headers
|
||||
# @return [Object] value of the given header name
|
||||
def [](key)
|
||||
headers[key]
|
||||
end
|
||||
|
||||
# @param key [Object] key of header to write
|
||||
# @param value [Object] value of header
|
||||
def []=(key, value)
|
||||
headers[key] = value
|
||||
end
|
||||
|
||||
# Marshal serialization support.
|
||||
#
|
||||
# @return [Hash] the hash ready to be serialized in Marshal.
|
||||
def marshal_dump
|
||||
{
|
||||
:method => method,
|
||||
:body => body,
|
||||
:headers => headers,
|
||||
:path => path,
|
||||
:params => params,
|
||||
:options => options
|
||||
http_method: http_method,
|
||||
body: body,
|
||||
headers: headers,
|
||||
path: path,
|
||||
params: params,
|
||||
options: options
|
||||
}
|
||||
end
|
||||
|
||||
# Marshal serialization support.
|
||||
# Restores the instance variables according to the +serialised+.
|
||||
# @param serialised [Hash] the serialised object.
|
||||
def marshal_load(serialised)
|
||||
self.method = serialised[:method]
|
||||
self.body = serialised[:body]
|
||||
self.http_method = serialised[:http_method]
|
||||
self.body = serialised[:body]
|
||||
self.headers = serialised[:headers]
|
||||
self.path = serialised[:path]
|
||||
self.params = serialised[:params]
|
||||
self.path = serialised[:path]
|
||||
self.params = serialised[:params]
|
||||
self.options = serialised[:options]
|
||||
end
|
||||
|
||||
# ENV Keys
|
||||
# :method - a symbolized request method (:get, :post)
|
||||
# :body - the request body that will eventually be converted to a string.
|
||||
# :url - URI instance for the current request.
|
||||
# :status - HTTP response status code
|
||||
# :request_headers - hash of HTTP Headers to be sent to the server
|
||||
# :response_headers - Hash of HTTP headers from the server
|
||||
# :parallel_manager - sent if the connection is in parallel mode
|
||||
# :request - Hash of options for configuring the request.
|
||||
# :timeout - open/read timeout Integer in seconds
|
||||
# :open_timeout - read timeout Integer in seconds
|
||||
# :proxy - Hash of proxy options
|
||||
# :uri - Proxy Server URI
|
||||
# :user - Proxy server username
|
||||
# :password - Proxy server password
|
||||
# :ssl - Hash of options for configuring SSL requests.
|
||||
# @return [Env] the Env for this Request
|
||||
def to_env(connection)
|
||||
Env.new(method, body, connection.build_exclusive_url(path, params),
|
||||
options, headers, connection.ssl, connection.parallel_manager)
|
||||
Env.new(http_method, body, connection.build_exclusive_url(path, params),
|
||||
options, headers, connection.ssl, connection.parallel_manager)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require 'faraday/request/authorization'
|
||||
require 'faraday/request/instrumentation'
|
||||
require 'faraday/request/json'
|
||||
require 'faraday/request/url_encoded'
|
||||
|
@ -1,41 +1,54 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Faraday
|
||||
class Request::Authorization < Faraday::Middleware
|
||||
KEY = "Authorization".freeze unless defined? KEY
|
||||
class Request
|
||||
# Request middleware for the Authorization HTTP header
|
||||
class Authorization < Faraday::Middleware
|
||||
KEY = 'Authorization'
|
||||
|
||||
# Public
|
||||
def self.header(type, token)
|
||||
case token
|
||||
when String, Symbol
|
||||
"#{type} #{token}"
|
||||
when Hash
|
||||
build_hash(type.to_s, token)
|
||||
else
|
||||
raise ArgumentError, "Can't build an Authorization #{type} header from #{token.inspect}"
|
||||
# @param app [#call]
|
||||
# @param type [String, Symbol] Type of Authorization
|
||||
# @param params [Array<String, Proc, #call>] parameters to build the Authorization header.
|
||||
# If the type is `:basic`, then these can be a login and password pair.
|
||||
# Otherwise, a single value is expected that will be appended after the type.
|
||||
# This value can be a proc or an object responding to `.call`, in which case
|
||||
# it will be invoked on each request.
|
||||
def initialize(app, type, *params)
|
||||
@type = type
|
||||
@params = params
|
||||
super(app)
|
||||
end
|
||||
end
|
||||
|
||||
# Internal
|
||||
def self.build_hash(type, hash)
|
||||
comma = ", "
|
||||
values = []
|
||||
hash.each do |key, value|
|
||||
values << "#{key}=#{value.to_s.inspect}"
|
||||
# @param env [Faraday::Env]
|
||||
def on_request(env)
|
||||
return if env.request_headers[KEY]
|
||||
|
||||
env.request_headers[KEY] = header_from(@type, env, *@params)
|
||||
end
|
||||
"#{type} #{values * comma}"
|
||||
end
|
||||
|
||||
def initialize(app, type, token)
|
||||
@header_value = self.class.header(type, token)
|
||||
super(app)
|
||||
end
|
||||
private
|
||||
|
||||
# Public
|
||||
def call(env)
|
||||
unless env.request_headers[KEY]
|
||||
env.request_headers[KEY] = @header_value
|
||||
# @param type [String, Symbol]
|
||||
# @param env [Faraday::Env]
|
||||
# @param params [Array]
|
||||
# @return [String] a header value
|
||||
def header_from(type, env, *params)
|
||||
if type.to_s.casecmp('basic').zero? && params.size == 2
|
||||
Utils.basic_header_from(*params)
|
||||
elsif params.size != 1
|
||||
raise ArgumentError, "Unexpected params received (got #{params.size} instead of 1)"
|
||||
else
|
||||
value = params.first
|
||||
if (value.is_a?(Proc) && value.arity == 1) || (value.respond_to?(:call) && value.method(:call).arity == 1)
|
||||
value = value.call(env)
|
||||
elsif value.is_a?(Proc) || value.respond_to?(:call)
|
||||
value = value.call
|
||||
end
|
||||
"#{type} #{value}"
|
||||
end
|
||||
end
|
||||
@app.call(env)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Faraday::Request.register_middleware(authorization: Faraday::Request::Authorization)
|
||||
|
@ -1,13 +0,0 @@
|
||||
require 'base64'
|
||||
|
||||
module Faraday
|
||||
class Request::BasicAuthentication < Request.load_middleware(:authorization)
|
||||
# Public
|
||||
def self.header(login, pass)
|
||||
value = Base64.encode64([login, pass].join(':'))
|
||||
value.gsub!("\n", '')
|
||||
super(:Basic, value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|