mirror of
https://github.com/spring-cloud/spring-cloud-netflix.git
synced 2026-09-20 00:59:01 +00:00
Compare commits
737
Commits
1.1.x
...
v1.4.1.RELEASE
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
afa69e7b8b | ||
|
|
8d18dfbc3d | ||
|
|
d2155dc561 | ||
|
|
882219f962 | ||
|
|
8dfcdbed76 | ||
|
|
8017995ba3 | ||
|
|
ca399ffd0b | ||
|
|
a4c995a394 | ||
|
|
9deab33dc1 | ||
|
|
94358489ef | ||
|
|
504720e366 | ||
|
|
07de257371 | ||
|
|
0401ad96e5 | ||
|
|
efb91874f8 | ||
|
|
ebdcca4fa8 | ||
|
|
2287e0caf2 | ||
|
|
dde7ff4c45 | ||
|
|
6c52269dc5 | ||
|
|
736a3e4610 | ||
|
|
1e87b50998 | ||
|
|
f59a228daf | ||
|
|
db3a412c8a | ||
|
|
202f96876e | ||
|
|
2489d5e234 | ||
|
|
64ad2ba20c | ||
|
|
60c4e1b007 | ||
|
|
6e3b4fdd7b | ||
|
|
62a8f72b98 | ||
|
|
89441ac126 | ||
|
|
d9f7842aec | ||
|
|
65762b1dce | ||
|
|
ba24811356 | ||
|
|
573d52119a | ||
|
|
3cafacc3e9 | ||
|
|
a805b53d86 | ||
|
|
c0c59320a5 | ||
|
|
271bfcfb31 | ||
|
|
798a4bb2b9 | ||
|
|
df7f31098c | ||
|
|
fa9524f9c9 | ||
|
|
1368f059bb | ||
|
|
8701060355 | ||
|
|
ee067ed3f2 | ||
|
|
b57dedb74a | ||
|
|
d7c13adac0 | ||
|
|
bd2473a7bf | ||
|
|
de16cb71f1 | ||
|
|
ffc431753b | ||
|
|
28de82e63c | ||
|
|
6c65266f05 | ||
|
|
6ffe2f1af2 | ||
|
|
68ce0cf25f | ||
|
|
7cff8ddbd3 | ||
|
|
9d1eed6a82 | ||
|
|
eeadc829b9 | ||
|
|
efe040549c | ||
|
|
5bc9e65cdd | ||
|
|
d6a604051f | ||
|
|
51a03b30f2 | ||
|
|
561f3b2fbf | ||
|
|
3a7af2c8fe | ||
|
|
26c7c362d2 | ||
|
|
0642ea52c9 | ||
|
|
957e33207e | ||
|
|
a39fe345b5 | ||
|
|
40e72ddb8c | ||
|
|
195c3fd75d | ||
|
|
c626bccad3 | ||
|
|
e295188075 | ||
|
|
7804e1bd59 | ||
|
|
6b6889b8b4 | ||
|
|
f809c119bd | ||
|
|
a806dc920f | ||
|
|
74fc02b2cc | ||
|
|
c1fc987d96 | ||
|
|
7f97a61ed5 | ||
|
|
05a3db3cc0 | ||
|
|
145b7ce69a | ||
|
|
c6fa162728 | ||
|
|
527dc2cc48 | ||
|
|
7672ee3666 | ||
|
|
7ca4f33dcf | ||
|
|
78e4cb4410 | ||
|
|
baa50c136a | ||
|
|
24d0bedf7d | ||
|
|
38b8786ede | ||
|
|
146513aeb0 | ||
|
|
4dc5088fb2 | ||
|
|
4c53fd2bf6 | ||
|
|
885425e2a3 | ||
|
|
b74c77b74f | ||
|
|
1bc8e8fd3a | ||
|
|
6dbdb1d8ed | ||
|
|
ea5585e381 | ||
|
|
ace95a3355 | ||
|
|
019615f376 | ||
|
|
a8d40afd58 | ||
|
|
ebe3800c75 | ||
|
|
30b98bea85 | ||
|
|
d9738a7ee5 | ||
|
|
d778b938e7 | ||
|
|
5e9fa908b4 | ||
|
|
91808c5fd9 | ||
|
|
0784b1ee19 | ||
|
|
b3cdd72754 | ||
|
|
4a8913f7ea | ||
|
|
8fbd73af1e | ||
|
|
1be2b2c295 | ||
|
|
02778fd670 | ||
|
|
985f014f40 | ||
|
|
2a63d07146 | ||
|
|
947d30c5c4 | ||
|
|
e71ce3395d | ||
|
|
f5e7be25dd | ||
|
|
c8d024c25f | ||
|
|
b519ad8d18 | ||
|
|
fcb1f5dc54 | ||
|
|
9b7a2f93ad | ||
|
|
9ce253b644 | ||
|
|
24cbbb9a11 | ||
|
|
ed57c8f0fa | ||
|
|
07b99704ee | ||
|
|
9440e4d153 | ||
|
|
1840647458 | ||
|
|
c3114e65bf | ||
|
|
c2aa60f292 | ||
|
|
e4e29e1316 | ||
|
|
63f975ad43 | ||
|
|
a01fc5b758 | ||
|
|
481e31220b | ||
|
|
040ce4db81 | ||
|
|
8fb89b25d9 | ||
|
|
099c9024bb | ||
|
|
bd7e75f4b3 | ||
|
|
b3d61a6de7 | ||
|
|
e5b14e4177 | ||
|
|
0de86d3faf | ||
|
|
aa2159b7e5 | ||
|
|
1c30c24639 | ||
|
|
cd2ed8eeee | ||
|
|
112a3e27a6 | ||
|
|
bbac5a730f | ||
|
|
348c96bf13 | ||
|
|
fd783734ac | ||
|
|
fc4f375a65 | ||
|
|
1d6b47ceef | ||
|
|
cda1c876b2 | ||
|
|
6112ea2558 | ||
|
|
37129671b6 | ||
|
|
97f1655ff7 | ||
|
|
1cf509c365 | ||
|
|
6b530b0269 | ||
|
|
e525ab7d64 | ||
|
|
31d19fd51a | ||
|
|
74540e9f4d | ||
|
|
afc8a99d7e | ||
|
|
859a79ad35 | ||
|
|
6ab27328cc | ||
|
|
72c31ca524 | ||
|
|
4d69941593 | ||
|
|
0d19a55ddd | ||
|
|
ceef16f0c8 | ||
|
|
187f34dfee | ||
|
|
96b1e40c0a | ||
|
|
63d77a8b81 | ||
|
|
8022342e81 | ||
|
|
6379e15ce1 | ||
|
|
5b075623ff | ||
|
|
46cbaa053c | ||
|
|
ffc33b4c69 | ||
|
|
1d3773ab5e | ||
|
|
a968b4bec8 | ||
|
|
65902d11e4 | ||
|
|
050dc31c97 | ||
|
|
aae96faa76 | ||
|
|
0dea44acf4 | ||
|
|
b158f6f2b7 | ||
|
|
11da3d4e3c | ||
|
|
5f4fc247a4 | ||
|
|
78ba3c5394 | ||
|
|
781fa3ea0c | ||
|
|
a08b661af6 | ||
|
|
d0009ba05a | ||
|
|
ab9b5ba3ec | ||
|
|
70d39aae7b | ||
|
|
56ccc46f5f | ||
|
|
ff33905704 | ||
|
|
69739420fa | ||
|
|
2b593bc742 | ||
|
|
eb1b7baf1c | ||
|
|
05eef0e8f5 | ||
|
|
c2c634707e | ||
|
|
0579b326be | ||
|
|
70a26fec26 | ||
|
|
02ec9bb98e | ||
|
|
b2f608b884 | ||
|
|
a496dc8170 | ||
|
|
8ad15399d6 | ||
|
|
8e11514e7c | ||
|
|
89cb5573d9 | ||
|
|
5856d76e65 | ||
|
|
86b1f6ede3 | ||
|
|
572305d7c8 | ||
|
|
2aa93abc0f | ||
|
|
e2954d0117 | ||
|
|
497b95ec64 | ||
|
|
02e7594de5 | ||
|
|
7efa68e853 | ||
|
|
003fefa61d | ||
|
|
5c035768b0 | ||
|
|
9a878b0fbd | ||
|
|
779b026e6b | ||
|
|
ccae4e4ce2 | ||
|
|
d39a22945b | ||
|
|
aac7799e3e | ||
|
|
310f558e9c | ||
|
|
1a0428da97 | ||
|
|
4783b07407 | ||
|
|
0334c5a289 | ||
|
|
12a9a2e485 | ||
|
|
f9424231a9 | ||
|
|
d4ba70db5c | ||
|
|
a59f5e1c4f | ||
|
|
02316d4ee0 | ||
|
|
4f148de74d | ||
|
|
9f10d5207f | ||
|
|
484bde10b3 | ||
|
|
8281757da8 | ||
|
|
4e25e2eb81 | ||
|
|
f552426304 | ||
|
|
15295dd294 | ||
|
|
1405a1894b | ||
|
|
a1c8cfe967 | ||
|
|
f0b424c84f | ||
|
|
582746587b | ||
|
|
a69fe71406 | ||
|
|
13155ce64c | ||
|
|
c5d95ca805 | ||
|
|
0b2fc81be4 | ||
|
|
9e180b8ba7 | ||
|
|
e08a6e8a48 | ||
|
|
7d06c140fa | ||
|
|
d914406c84 | ||
|
|
31a3ff61e8 | ||
|
|
3773047a06 | ||
|
|
a1d089038d | ||
|
|
562e604cee | ||
|
|
71c906b00b | ||
|
|
6d22a407cc | ||
|
|
bdfbe3a87e | ||
|
|
cb3613db71 | ||
|
|
7dbfe15d1a | ||
|
|
ccf8f86c8d | ||
|
|
7425f974cb | ||
|
|
58e62ebd06 | ||
|
|
0de72d2dad | ||
|
|
7898ad772b | ||
|
|
25a1881127 | ||
|
|
34350cb7ec | ||
|
|
e130ec03ad | ||
|
|
0cd544b4ce | ||
|
|
6362be8196 | ||
|
|
aa4d9d62bc | ||
|
|
cd44a52084 | ||
|
|
98fd3c7379 | ||
|
|
73b5609e63 | ||
|
|
fe06370aa2 | ||
|
|
a1b1a7d702 | ||
|
|
ccdf0d9f95 | ||
|
|
287ce9c0ff | ||
|
|
b8ed846247 | ||
|
|
2a9a62d0f6 | ||
|
|
377bccf12a | ||
|
|
030a8e7111 | ||
|
|
189d926c6a | ||
|
|
d993fe217c | ||
|
|
8983c9bce1 | ||
|
|
ced1f36d19 | ||
|
|
e698fa664b | ||
|
|
ab2cf59bf6 | ||
|
|
a315ac9e54 | ||
|
|
c6347ec9c8 | ||
|
|
ac8379af29 | ||
|
|
69622e909b | ||
|
|
910167de09 | ||
|
|
29fb58de84 | ||
|
|
dbf17dea18 | ||
|
|
7fcbc0fd5a | ||
|
|
32c79b7a91 | ||
|
|
1647fc3017 | ||
|
|
17f43fd138 | ||
|
|
a708ceefe8 | ||
|
|
1f12902f78 | ||
|
|
cebbcf592e | ||
|
|
0c7994c502 | ||
|
|
d412df5883 | ||
|
|
cca3744396 | ||
|
|
c98c50291c | ||
|
|
e9035ab3e0 | ||
|
|
d8eb4432b9 | ||
|
|
8dcf544079 | ||
|
|
98cbf2f47f | ||
|
|
e041d87b5a | ||
|
|
5440aca212 | ||
|
|
834980eae7 | ||
|
|
a61822f9c9 | ||
|
|
6290859f81 | ||
|
|
62408af5ea | ||
|
|
711a8f0aae | ||
|
|
382988a06a | ||
|
|
88431bdbea | ||
|
|
62cb0ae28f | ||
|
|
31f5a2bb73 | ||
|
|
cb90cd9929 | ||
|
|
d402587ab2 | ||
|
|
02226f938e | ||
|
|
37309218ec | ||
|
|
03740f1369 | ||
|
|
d3314447a8 | ||
|
|
a8b170f51c | ||
|
|
a669a309f6 | ||
|
|
a628ba1b8f | ||
|
|
5bf87c4da1 | ||
|
|
b7c5b24d43 | ||
|
|
44bb4f2cc3 | ||
|
|
55a58eaf30 | ||
|
|
5d3ca843b6 | ||
|
|
ecf12aa0e7 | ||
|
|
427770a6d0 | ||
|
|
2d9604918a | ||
|
|
567a6c4a9f | ||
|
|
31b617b10f | ||
|
|
a6fe648a41 | ||
|
|
66163bee2b | ||
|
|
db7d352c97 | ||
|
|
cda92f4ba3 | ||
|
|
133613aeb1 | ||
|
|
15e01dfe5d | ||
|
|
6df7190472 | ||
|
|
5212d138f8 | ||
|
|
465582d20c | ||
|
|
fae60e9fae | ||
|
|
c89645e069 | ||
|
|
6465cf4f3e | ||
|
|
54c052c5eb | ||
|
|
465b8273eb | ||
|
|
129e0ce4b5 | ||
|
|
309b280d0d | ||
|
|
99d9b17a2d | ||
|
|
208a741823 | ||
|
|
c953d59b5a | ||
|
|
c0fefc4d1a | ||
|
|
0fcc979fe1 | ||
|
|
cb9d9bd8a1 | ||
|
|
1d5a68c9e7 | ||
|
|
8e3f45b899 | ||
|
|
ea87b9bfc2 | ||
|
|
df91ff7735 | ||
|
|
652fe97a1b | ||
|
|
06565765b8 | ||
|
|
fc9c7dcdfc | ||
|
|
72f1ce9096 | ||
|
|
0abf69dc1d | ||
|
|
a4f30ebf1d | ||
|
|
ed137a93ce | ||
|
|
e73ee5f690 | ||
|
|
de886d6c3d | ||
|
|
050d5f898f | ||
|
|
e3341a19cd | ||
|
|
8f5e3ad7c0 | ||
|
|
384c9b9725 | ||
|
|
981fa5db9a | ||
|
|
c417a9a55e | ||
|
|
52ab4d3ecd | ||
|
|
62bc2758eb | ||
|
|
731c8bf0fd | ||
|
|
d78da7b994 | ||
|
|
c795bd16a7 | ||
|
|
7237275f5d | ||
|
|
3e7b196950 | ||
|
|
919c8fa72a | ||
|
|
bcb97bce1c | ||
|
|
64d8883210 | ||
|
|
0af99769ad | ||
|
|
c8fded80d5 | ||
|
|
c6a3157fd8 | ||
|
|
8afb02cf03 | ||
|
|
675e4c1031 | ||
|
|
d21d03b738 | ||
|
|
ff70c08fb9 | ||
|
|
506a9a44af | ||
|
|
10e8634d38 | ||
|
|
a1577127b7 | ||
|
|
5a1f1d2c64 | ||
|
|
a915651797 | ||
|
|
133d054fc8 | ||
|
|
c87f6ca983 | ||
|
|
a2e1836e24 | ||
|
|
53649f6020 | ||
|
|
601e220411 | ||
|
|
ed6c4b1925 | ||
|
|
46ee0ce161 | ||
|
|
71b3110acf | ||
|
|
e6f2db70ef | ||
|
|
65829c437c | ||
|
|
f27defbdf4 | ||
|
|
41bf8eadfd | ||
|
|
d76bd84466 | ||
|
|
ca244c5dbb | ||
|
|
ff0ac568f4 | ||
|
|
5d0b2fae86 | ||
|
|
f8a8b3cf31 | ||
|
|
88396411f7 | ||
|
|
42931f9300 | ||
|
|
a5bd17c0c0 | ||
|
|
a5e5f81d88 | ||
|
|
66da2d8f48 | ||
|
|
b619ee00bf | ||
|
|
899d8bc24d | ||
|
|
bf586ba2cb | ||
|
|
22248dd9e9 | ||
|
|
6dfc7a815b | ||
|
|
b27ba0434a | ||
|
|
3b41b5e15f | ||
|
|
8029deb1e4 | ||
|
|
5ee9990d93 | ||
|
|
8a9c5fe4a1 | ||
|
|
45d769b05b | ||
|
|
af032476a8 | ||
|
|
c52309ad59 | ||
|
|
8526371d25 | ||
|
|
7f4a6fd94c | ||
|
|
6fc5d1aea3 | ||
|
|
1860b26491 | ||
|
|
fccbe1aff4 | ||
|
|
fa45a9f3a4 | ||
|
|
98cd592f3e | ||
|
|
64b81ff5a4 | ||
|
|
f3deb04521 | ||
|
|
ab454f1e1b | ||
|
|
bd6d732fc7 | ||
|
|
4eac8a5a47 | ||
|
|
82dd2ebd05 | ||
|
|
1cd194a1e1 | ||
|
|
f26a5554d5 | ||
|
|
c380039b3e | ||
|
|
9125f54897 | ||
|
|
08fe5a89e9 | ||
|
|
2e46bdf9d2 | ||
|
|
18b9c46a8b | ||
|
|
a99e23f9fc | ||
|
|
8b089222bd | ||
|
|
5b25c9ad1a | ||
|
|
ec80797694 | ||
|
|
8b7d6848b3 | ||
|
|
edb14e2a6b | ||
|
|
6a82644153 | ||
|
|
ac3401bc4f | ||
|
|
9da71b1655 | ||
|
|
ed31004aa8 | ||
|
|
fe50674c8e | ||
|
|
1fe1ea58f6 | ||
|
|
b3f1a30027 | ||
|
|
111a95d262 | ||
|
|
35528a7199 | ||
|
|
927b753262 | ||
|
|
8359016981 | ||
|
|
5372fe9d6e | ||
|
|
8814da3b78 | ||
|
|
2d748bf63c | ||
|
|
eff1f0d77a | ||
|
|
aec8ec43ef | ||
|
|
93cd1e7a4a | ||
|
|
ea4e5d76e0 | ||
|
|
e6298f8107 | ||
|
|
704a874e84 | ||
|
|
428ed6c88c | ||
|
|
dd62879b7b | ||
|
|
14a31cd5f8 | ||
|
|
8a7df36d88 | ||
|
|
fe56d96295 | ||
|
|
fabf90bed4 | ||
|
|
c3a84f1b73 | ||
|
|
5a5a1aaa7f | ||
|
|
aef753a3a0 | ||
|
|
08028db982 | ||
|
|
85b2fe37e2 | ||
|
|
81959b5ef3 | ||
|
|
657d9bb8e8 | ||
|
|
2dfee97563 | ||
|
|
7f075d11ee | ||
|
|
a38b7b71ac | ||
|
|
139ab95438 | ||
|
|
71ac67f9f1 | ||
|
|
0e1b856fc5 | ||
|
|
0235d77932 | ||
|
|
6e1ca54edc | ||
|
|
274213bc83 | ||
|
|
5a42cb1b2b | ||
|
|
ee99efba36 | ||
|
|
b16a95d0f2 | ||
|
|
2c8bac5666 | ||
|
|
8e6b643829 | ||
|
|
cc24d00fde | ||
|
|
ddc1069e86 | ||
|
|
c9704d3c97 | ||
|
|
34775eb8db | ||
|
|
90d6490fea | ||
|
|
acb9e5ebdf | ||
|
|
12fff7c97f | ||
|
|
acb12ea7d4 | ||
|
|
e94c35d331 | ||
|
|
a685a72660 | ||
|
|
4aaa7d0555 | ||
|
|
2f09aa291b | ||
|
|
bdf2f25259 | ||
|
|
5a0af27115 | ||
|
|
18c4bc060b | ||
|
|
d23aa67f7b | ||
|
|
5a127d0700 | ||
|
|
aa160940df | ||
|
|
c8829f2aa4 | ||
|
|
0985c8f092 | ||
|
|
279edf0665 | ||
|
|
50fac81a30 | ||
|
|
99b86e7a29 | ||
|
|
151e021ef9 | ||
|
|
dd97b6a4b7 | ||
|
|
f30e8b7b46 | ||
|
|
ba3923d706 | ||
|
|
622cc83204 | ||
|
|
8e18c3d3b6 | ||
|
|
0adbd5662f | ||
|
|
f00d1159c8 | ||
|
|
4c90ce437e | ||
|
|
e109093b83 | ||
|
|
4ba3411292 | ||
|
|
8e1be54759 | ||
|
|
be6f2634b5 | ||
|
|
7191e820a2 | ||
|
|
9eed249028 | ||
|
|
bbff2b24d0 | ||
|
|
b91fa734d2 | ||
|
|
302eae6aeb | ||
|
|
088afa6d71 | ||
|
|
5f96c415e3 | ||
|
|
6c9fa68d5a | ||
|
|
bc4f5a3788 | ||
|
|
f094ecfd88 | ||
|
|
13268f7c63 | ||
|
|
aa820ce347 | ||
|
|
6310059531 | ||
|
|
6f92859608 | ||
|
|
758ff920fc | ||
|
|
7ba8063940 | ||
|
|
61de118130 | ||
|
|
4bf64f7005 | ||
|
|
85eab87dce | ||
|
|
5ba0cc5860 | ||
|
|
08aae5be3b | ||
|
|
071bad218a | ||
|
|
027e7fc5c6 | ||
|
|
498e8a1486 | ||
|
|
09c6bc1d12 | ||
|
|
62521f38e8 | ||
|
|
a6785afa5a | ||
|
|
7ebb46a092 | ||
|
|
a9fcc0a002 | ||
|
|
08ed3c7fc4 | ||
|
|
3c1a23e7b1 | ||
|
|
c7403361fd | ||
|
|
d9efb0a84d | ||
|
|
4f81255970 | ||
|
|
e556ef84a4 | ||
|
|
9587003d44 | ||
|
|
8f7ea41fe8 | ||
|
|
b4284d18d5 | ||
|
|
048d401309 | ||
|
|
85a1f3ec20 | ||
|
|
959b007269 | ||
|
|
9b550f7293 | ||
|
|
dc26b1efa8 | ||
|
|
d356e57134 | ||
|
|
c5b0ffe310 | ||
|
|
1ffa7825ed | ||
|
|
d95f3cd1c5 | ||
|
|
6ba64d97c8 | ||
|
|
fa508a2c7e | ||
|
|
d04df564f0 | ||
|
|
5f192dd6ce | ||
|
|
6819922d26 | ||
|
|
ec726e863c | ||
|
|
610754981b | ||
|
|
485034dec0 | ||
|
|
9316124733 | ||
|
|
1b9d91dc1e | ||
|
|
15550e1855 | ||
|
|
43adc84dfd | ||
|
|
8d6aeebb61 | ||
|
|
1c8c0cfbf0 | ||
|
|
047e25f7f7 | ||
|
|
258e53a402 | ||
|
|
c8c4dfba6e | ||
|
|
0bed544188 | ||
|
|
3cc07d36a2 | ||
|
|
78b53a5215 | ||
|
|
886382f568 | ||
|
|
a9dd1b4568 | ||
|
|
323243f1ec | ||
|
|
25366c2a37 | ||
|
|
b98c9ebc1d | ||
|
|
cb44cd2e33 | ||
|
|
1fefd410be | ||
|
|
f5bb5ff9c1 | ||
|
|
e2e7c490db | ||
|
|
68cf346025 | ||
|
|
7927372b75 | ||
|
|
ca2c9018c6 | ||
|
|
9ed5621a78 | ||
|
|
58cf528463 | ||
|
|
4602fc1105 | ||
|
|
b9e2a1b21e | ||
|
|
f76f294f7f | ||
|
|
64fff7edd2 | ||
|
|
beb84dbe07 | ||
|
|
d9768b5378 | ||
|
|
34befdf1d2 | ||
|
|
21df6e43a3 | ||
|
|
bc7b8367a9 | ||
|
|
c7a6720593 | ||
|
|
cf0c3bda92 | ||
|
|
2db2a033ab | ||
|
|
cf72184883 | ||
|
|
62344dccda | ||
|
|
03caa1b8ce | ||
|
|
97c8f4c026 | ||
|
|
674e45f473 | ||
|
|
36fec07a17 | ||
|
|
0c694a018c | ||
|
|
f37b513f21 | ||
|
|
56c7466e64 | ||
|
|
519be4db06 | ||
|
|
4255c5578b | ||
|
|
5e698278a8 | ||
|
|
b09b0bcdbd | ||
|
|
909a51000f | ||
|
|
306b43624b | ||
|
|
ec22c23bc5 | ||
|
|
a4ba11eabb | ||
|
|
58559aff16 | ||
|
|
3e3511ac44 | ||
|
|
36ae029c18 | ||
|
|
4dc016f3e5 | ||
|
|
d90e99216b | ||
|
|
67838807e8 | ||
|
|
dc2cdd55c0 | ||
|
|
5b0f6013e8 | ||
|
|
52c956831e | ||
|
|
4f93897b71 | ||
|
|
5676c8f753 | ||
|
|
eed72600c5 | ||
|
|
35ca9691d2 | ||
|
|
072f7760a4 | ||
|
|
3c1599a1f4 | ||
|
|
90808124f5 | ||
|
|
3c96fcc888 | ||
|
|
d21a7ef8e2 | ||
|
|
b1ec12af43 | ||
|
|
7929dfec0f | ||
|
|
8bb223f6e3 | ||
|
|
8b911e6074 | ||
|
|
10a3d1df69 | ||
|
|
8cf581d496 | ||
|
|
0095ba8984 | ||
|
|
6f32aa95a7 | ||
|
|
afb43c5883 | ||
|
|
85ef54a9a1 | ||
|
|
2f581a1181 | ||
|
|
110f872779 | ||
|
|
54931afa8b | ||
|
|
2bdd2dab78 | ||
|
|
2908323702 | ||
|
|
5d37bfe2fa | ||
|
|
0b0bdb17aa | ||
|
|
e5b2ae967b | ||
|
|
c03ae0ab47 | ||
|
|
5d664cd00d | ||
|
|
00ed67f1b7 | ||
|
|
277ded519d | ||
|
|
7835c4047b | ||
|
|
ab0af4554c | ||
|
|
926414a014 | ||
|
|
3991c8c5f7 | ||
|
|
cb4d60ca8d | ||
|
|
7ebe1977d8 | ||
|
|
5e9c71e0e3 | ||
|
|
723287b305 | ||
|
|
46b2e45fd5 | ||
|
|
390999e042 | ||
|
|
8a7e165214 | ||
|
|
5742f5e811 | ||
|
|
52b6578fcf | ||
|
|
2f205f20c5 | ||
|
|
e1df7c3b4d | ||
|
|
7c2f806745 | ||
|
|
6877f0898f | ||
|
|
74ac6eae8f | ||
|
|
75610e92a7 | ||
|
|
7dc8b86001 | ||
|
|
dfbfcfecc7 | ||
|
|
7ac81f8fd5 | ||
|
|
fd36a0cc72 | ||
|
|
398c5aa009 | ||
|
|
7a90c0cd88 | ||
|
|
cfcca5f881 | ||
|
|
ca67265c79 | ||
|
|
b422fe297c | ||
|
|
856c47138a | ||
|
|
83cdf647c9 | ||
|
|
ff46a7dc50 | ||
|
|
532627fad2 | ||
|
|
aa23660d0c | ||
|
|
02948657a4 | ||
|
|
cfb13ac59f | ||
|
|
00aee4be00 | ||
|
|
a669975fb0 | ||
|
|
161d719dba | ||
|
|
dd220458f8 | ||
|
|
d4163dde8e | ||
|
|
3bd563e495 | ||
|
|
525c5852fb | ||
|
|
69501bd110 | ||
|
|
f6cd2d279d | ||
|
|
9cab69a968 | ||
|
|
243c13b7ee | ||
|
|
09cc1f2db0 | ||
|
|
7d295bdc8b |
@@ -14,3 +14,7 @@ _site/
|
||||
*.ipr
|
||||
*.iws
|
||||
.factorypath
|
||||
*.log
|
||||
.shelf
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.3.9/apache-maven-3.3.9-bin.zip
|
||||
distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.0/apache-maven-3.5.0-bin.zip
|
||||
-33
@@ -1,33 +0,0 @@
|
||||
sudo: false
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.m2
|
||||
language: java
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
services:
|
||||
- rabbitmq
|
||||
before_install:
|
||||
- git config user.name "$GIT_NAME"
|
||||
- git config user.email "$GIT_EMAIL"
|
||||
- git config credential.helper "store --file=.git/credentials"
|
||||
- echo "https://$GH_TOKEN:@github.com" > .git/credentials
|
||||
- gem install asciidoctor
|
||||
install:
|
||||
- ./mvnw install -P docs -q -U -DskipTests=true $MVN_LOCAL_OPTS
|
||||
- '[ "${MVN_GOAL}" == "deploy" ] && ./docs/src/main/asciidoc/ghpages.sh || echo "Not updating docs"'
|
||||
script:
|
||||
- './mvnw -s .settings.xml $MVN_GOAL $MVN_PROFILE -nsu $MVN_LOCAL_OPTS'
|
||||
env:
|
||||
global:
|
||||
- MVN_LOCAL_OPTS="-Dmaven.source.skip=true -Dmaven.javadoc.skip=true -Dmaven.test.redirectTestOutputToFile=true"
|
||||
- GIT_NAME="Dave Syer"
|
||||
- GIT_EMAIL=dsyer@pivotal.io
|
||||
- CI_DEPLOY_USERNAME=buildmaster
|
||||
- FEATURE_BRANCH=$(echo ${TRAVIS_BRANCH} | grep -q "^.*/.*$" && echo true || echo false)
|
||||
- SPRING_CLOUD_BUILD=$(echo ${TRAVIS_REPO_SLUG} | grep -q "^spring-cloud/.*$" && echo true || echo false)
|
||||
- MVN_GOAL=install
|
||||
- VERSION=$(mvn validate | grep Building | head -1 | sed -e 's/.* //')
|
||||
- MILESTONE=$(echo ${VERSION} | egrep 'M|RC' && echo true || echo false)
|
||||
- MVN_PROFILE=$([ "${MILESTONE}" == "true" ] && echo -P milestone)
|
||||
- secure: aeLXRC5oFSddwnZt1/7G2/OHr7jDbxz0ET7sej3I+eSbe3N5vbzQ6FC08es4l89l54ciXd90I1g2BMw7DTYKOO373FP78XPdAEbifJTU4DGd6fCELmoTtUPhjunBIk7E49hisPbv82892IYYA7qi/hzG548cPyZ1IgiJjq0NCsc=
|
||||
+8
-39
@@ -1,6 +1,9 @@
|
||||
// Do not edit this file (e.g. go instead to docs/src/main/asciidoc)
|
||||
// Do not edit this file (e.g. go instead to src/main/asciidoc)
|
||||
|
||||
image::https://circleci.com/gh/spring-cloud/spring-cloud-netflix/tree/master.svg?style=svg["CircleCI", link="https://circleci.com/gh/spring-cloud/spring-cloud-netflix/tree/master"]
|
||||
image::https://codecov.io/gh/spring-cloud/spring-cloud-netflix/branch/master/graph/badge.svg["Codecov", link="https://codecov.io/gh/spring-cloud/spring-cloud-netflix/branch/master"]
|
||||
image::https://api.codacy.com/project/badge/Grade/a6885a06921e4f72a0df0b7aabd6d118["Codacy code quality", link="https://www.codacy.com/app/Spring-Cloud/spring-cloud-netflix?utm_source=github.com&utm_medium=referral&utm_content=spring-cloud/spring-cloud-netflix&utm_campaign=Badge_Grade"]
|
||||
|
||||
image::https://travis-ci.org/spring-cloud/spring-cloud-netflix.svg?branch=master[Build Status, link=https://travis-ci.org/spring-cloud/spring-cloud-netflix]
|
||||
|
||||
This project provides Netflix OSS integrations for Spring Boot apps through autoconfiguration
|
||||
and binding to the Spring Environment and other Spring programming model idioms. With a few
|
||||
@@ -82,11 +85,11 @@ a modified file in the correct place. Just commit it and push the change.
|
||||
If you don't have an IDE preference we would recommend that you use
|
||||
http://www.springsource.com/developer/sts[Spring Tools Suite] or
|
||||
http://eclipse.org[Eclipse] when working with the code. We use the
|
||||
http://eclipse.org/m2e/[m2eclipe] eclipse plugin for maven support. Other IDEs and tools
|
||||
http://eclipse.org/m2e/[m2eclipse] eclipse plugin for maven support. Other IDEs and tools
|
||||
should also work without issue as long as they use Maven 3.3.3 or better.
|
||||
|
||||
==== Importing into eclipse with m2eclipse
|
||||
We recommend the http://eclipse.org/m2e/[m2eclipe] eclipse plugin when working with
|
||||
We recommend the http://eclipse.org/m2e/[m2eclipse] eclipse plugin when working with
|
||||
eclipse. If you don't already have m2eclipse installed it is available from the "eclipse
|
||||
marketplace".
|
||||
|
||||
@@ -113,40 +116,6 @@ from the `file` menu.
|
||||
|
||||
|
||||
|
||||
==== Adding Project Lombok Agent
|
||||
|
||||
Spring Cloud uses http://projectlombok.org/features/index.html[Project Lombok]
|
||||
to generate getters and setters etc. Compiling from the command line this
|
||||
shouldn't cause any problems, but in an IDE you need to add an agent
|
||||
to the JVM. Full instructions can be found in the Lombok website. The
|
||||
sign that you need to do this is a lot of compiler errors to do with
|
||||
missing methods and fields, e.g.
|
||||
|
||||
[indent=0]
|
||||
----
|
||||
The method getInitialStatus() is undefined for the type EurekaInstanceConfigBean EurekaDiscoveryClientConfiguration.java /spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/eureka line 120 Java Problem
|
||||
The method getInitialStatus() is undefined for the type EurekaInstanceConfigBean EurekaDiscoveryClientConfiguration.java /spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/eureka line 121 Java Problem
|
||||
The method setNonSecurePort(int) is undefined for the type EurekaInstanceConfigBean EurekaDiscoveryClientConfiguration.java /spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/eureka line 112 Java Problem
|
||||
The type EurekaInstanceConfigBean.IdentifyingDataCenterInfo must implement the inherited abstract method DataCenterInfo.getName() EurekaInstanceConfigBean.java /spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/eureka line 131 Java Problem
|
||||
The method getId() is undefined for the type ProxyRouteLocator.ProxyRouteSpec PreDecorationFilter.java /spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/pre line 60 Java Problem
|
||||
The method getLocation() is undefined for the type ProxyRouteLocator.ProxyRouteSpec PreDecorationFilter.java /spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/pre line 55 Java Problem
|
||||
----
|
||||
|
||||
==== Importing into Intellij
|
||||
Spring Cloud projects use annotation processing, particularly Lombok, which requires configuration
|
||||
or you will encounter compile problems. It also needs a specific version of maven and a profile
|
||||
enabled. Intellij 14.1+ requires some configuration to ensure these are setup properly.
|
||||
|
||||
1. Click Preferences, Plugins. *Ensure Lombok is installed*
|
||||
2. Click New, Project from Existing Sources, choose your spring-cloud project directory
|
||||
3. Choose Maven, and select Environment Settings. *Ensure you are using Maven 3.3.3*
|
||||
4. In the next screen, *Select the profile `spring`* click Next until Finish.
|
||||
5. Click Preferences, "Build, Execution, Deployment", Compiler, Annotation Processors. *Click Enable Annotation Processing*
|
||||
6. Click Build, Rebuild Project, and you are ready to go!
|
||||
|
||||
==== Importing into other IDEs
|
||||
Maven is well supported by most Java IDEs. Refer to you vendor documentation.
|
||||
|
||||
== Contributing
|
||||
|
||||
Spring Cloud is released under the non-restrictive Apache 2.0 license,
|
||||
@@ -157,7 +126,7 @@ follow the guidelines below.
|
||||
|
||||
=== Sign the Contributor License Agreement
|
||||
Before we accept a non-trivial patch or pull request we will need you to sign the
|
||||
https://support.springsource.com/spring_committer_signup[contributor's agreement].
|
||||
https://cla.pivotal.io/sign/spring[Contributor License Agreement].
|
||||
Signing the contributor's agreement does not grant anyone commit rights to the main
|
||||
repository, but it does mean that we can accept your contributions, and you will get an
|
||||
author credit if we do. Active contributors might be asked to join the core team, and
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
general:
|
||||
branches:
|
||||
ignore:
|
||||
- gh-pages # list of branches to ignore
|
||||
machine:
|
||||
java:
|
||||
version: oraclejdk8
|
||||
environment:
|
||||
_JAVA_OPTIONS: "-Xms1024m -Xmx2048m"
|
||||
dependencies:
|
||||
override:
|
||||
- cd spring-cloud-netflix-hystrix-contract && ../mvnw clean install
|
||||
- ./mvnw -s .settings.xml -U --fail-never dependency:go-offline || true
|
||||
test:
|
||||
override:
|
||||
- ./mvnw -s .settings.xml clean org.jacoco:jacoco-maven-plugin:prepare-agent install -U -P sonar -nsu --batch-mode -Dmaven.test.redirectTestOutputToFile=true -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn
|
||||
post:
|
||||
- find . -type f -regex ".*/spring-cloud-*.*/target/*.*" | cpio -pdm $CIRCLE_ARTIFACTS
|
||||
- mkdir -p $CIRCLE_TEST_REPORTS/junit/
|
||||
- find . -type f -regex ".*/target/.*-reports/.*" -exec cp {} $CIRCLE_TEST_REPORTS/junit/ \;
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
|
||||
notify:
|
||||
webhooks:
|
||||
# A list of hook hashes, containing the url field
|
||||
# gitter hook
|
||||
- url: https://webhooks.gitter.im/e/22e6bb4eb945dd61ba54
|
||||
@@ -0,0 +1,3 @@
|
||||
releaser:
|
||||
maven:
|
||||
buildCommand: ./scripts/build.sh {{systemProps}}
|
||||
+18
-9
@@ -1,11 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-netflix</artifactId>
|
||||
<version>1.1.3.BUILD-SNAPSHOT</version>
|
||||
<version>1.4.1.RELEASE</version>
|
||||
</parent>
|
||||
<artifactId>spring-cloud-netflix-docs</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
@@ -14,15 +14,16 @@
|
||||
<properties>
|
||||
<docs.main>spring-cloud-netflix</docs.main>
|
||||
<main.basedir>${basedir}/..</main.basedir>
|
||||
<docs.whitelisted.branches>1.2.x,1.3.x,1.4.x</docs.whitelisted.branches>
|
||||
</properties>
|
||||
<build>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
<configuration>
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
<configuration>
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<profiles>
|
||||
@@ -30,11 +31,19 @@
|
||||
<id>docs</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.asciidoctor</groupId>
|
||||
<artifactId>asciidoctor-maven-plugin</artifactId>
|
||||
<inherited>false</inherited>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>com.agilejava.docbkx</groupId>
|
||||
<artifactId>docbkx-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-antrun-plugin</artifactId>
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
image::https://travis-ci.org/spring-cloud/spring-cloud-netflix.svg?branch=master[Build Status, link=https://travis-ci.org/spring-cloud/spring-cloud-netflix]
|
||||
image::https://circleci.com/gh/spring-cloud/spring-cloud-netflix/tree/master.svg?style=svg["CircleCI", link="https://circleci.com/gh/spring-cloud/spring-cloud-netflix/tree/master"]
|
||||
image::https://codecov.io/gh/spring-cloud/spring-cloud-netflix/branch/master/graph/badge.svg["Codecov", link="https://codecov.io/gh/spring-cloud/spring-cloud-netflix/branch/master"]
|
||||
image::https://api.codacy.com/project/badge/Grade/a6885a06921e4f72a0df0b7aabd6d118["Codacy code quality", link="https://www.codacy.com/app/Spring-Cloud/spring-cloud-netflix?utm_source=github.com&utm_medium=referral&utm_content=spring-cloud/spring-cloud-netflix&utm_campaign=Badge_Grade"]
|
||||
|
||||
|
||||
include::intro.adoc[]
|
||||
|
||||
@@ -17,8 +20,6 @@ include::intro.adoc[]
|
||||
|
||||
include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/master/docs/src/main/asciidoc/building-jdk8.adoc[]
|
||||
|
||||
include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/master/docs/src/main/asciidoc/building-lombok.adoc[]
|
||||
|
||||
== Contributing
|
||||
|
||||
include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/master/docs/src/main/asciidoc/contributing.adoc[]
|
||||
|
||||
@@ -1,54 +1,330 @@
|
||||
#!/bin/bash -x
|
||||
|
||||
git remote set-url --push origin `git config remote.origin.url | sed -e 's/^git:/https:/'`
|
||||
set -e
|
||||
|
||||
if ! (git remote set-branches --add origin gh-pages && git fetch -q); then
|
||||
echo "No gh-pages, so not syncing"
|
||||
exit 0
|
||||
fi
|
||||
# Set default props like MAVEN_PATH, ROOT_FOLDER etc.
|
||||
function set_default_props() {
|
||||
# The script should be executed from the root folder
|
||||
ROOT_FOLDER=`pwd`
|
||||
echo "Current folder is ${ROOT_FOLDER}"
|
||||
|
||||
if ! [ -d docs/target/generated-docs ]; then
|
||||
echo "No gh-pages sources in docs/target/generated-docs, so not syncing"
|
||||
exit 0
|
||||
fi
|
||||
if [[ ! -e "${ROOT_FOLDER}/.git" ]]; then
|
||||
echo "You're not in the root folder of the project!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Find name of current branch
|
||||
###################################################################
|
||||
branch=$TRAVIS_BRANCH
|
||||
[ "$branch" == "" ] && branch=`git rev-parse --abbrev-ref HEAD`
|
||||
target=.
|
||||
if [ "$branch" != "master" ]; then target=./$branch; mkdir -p $target; fi
|
||||
# Prop that will let commit the changes
|
||||
COMMIT_CHANGES="no"
|
||||
MAVEN_PATH=${MAVEN_PATH:-}
|
||||
echo "Path to Maven is [${MAVEN_PATH}]"
|
||||
REPO_NAME=${PWD##*/}
|
||||
echo "Repo name is [${REPO_NAME}]"
|
||||
SPRING_CLOUD_STATIC_REPO=${SPRING_CLOUD_STATIC_REPO:-git@github.com:spring-cloud/spring-cloud-static.git}
|
||||
echo "Spring Cloud Static repo is [${SPRING_CLOUD_STATIC_REPO}"
|
||||
}
|
||||
|
||||
# Check if gh-pages exists and docs have been built
|
||||
function check_if_anything_to_sync() {
|
||||
git remote set-url --push origin `git config remote.origin.url | sed -e 's/^git:/https:/'`
|
||||
|
||||
if ! (git remote set-branches --add origin gh-pages && git fetch -q); then
|
||||
echo "No gh-pages, so not syncing"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if ! [ -d docs/target/generated-docs ] && ! [ "${BUILD}" == "yes" ]; then
|
||||
echo "No gh-pages sources in docs/target/generated-docs, so not syncing"
|
||||
exit 0
|
||||
fi
|
||||
}
|
||||
|
||||
function retrieve_current_branch() {
|
||||
# Code getting the name of the current branch. For master we want to publish as we did until now
|
||||
# http://stackoverflow.com/questions/1593051/how-to-programmatically-determine-the-current-checked-out-git-branch
|
||||
# If there is a branch already passed will reuse it - otherwise will try to find it
|
||||
CURRENT_BRANCH=${BRANCH}
|
||||
if [[ -z "${CURRENT_BRANCH}" ]] ; then
|
||||
CURRENT_BRANCH=$(git symbolic-ref -q HEAD)
|
||||
CURRENT_BRANCH=${CURRENT_BRANCH##refs/heads/}
|
||||
CURRENT_BRANCH=${CURRENT_BRANCH:-HEAD}
|
||||
fi
|
||||
echo "Current branch is [${CURRENT_BRANCH}]"
|
||||
git checkout ${CURRENT_BRANCH} || echo "Failed to check the branch... continuing with the script"
|
||||
}
|
||||
|
||||
# Switches to the provided value of the release version. We always prefix it with `v`
|
||||
function switch_to_tag() {
|
||||
git checkout v${VERSION}
|
||||
}
|
||||
|
||||
# Build the docs if switch is on
|
||||
function build_docs_if_applicable() {
|
||||
if [[ "${BUILD}" == "yes" ]] ; then
|
||||
./mvnw clean install -P docs -pl docs -DskipTests
|
||||
fi
|
||||
}
|
||||
|
||||
# Get the name of the `docs.main` property
|
||||
# Get whitelisted branches - assumes that a `docs` module is available under `docs` profile
|
||||
function retrieve_doc_properties() {
|
||||
MAIN_ADOC_VALUE=$("${MAVEN_PATH}"mvn -q \
|
||||
-Dexec.executable="echo" \
|
||||
-Dexec.args='${docs.main}' \
|
||||
--non-recursive \
|
||||
org.codehaus.mojo:exec-maven-plugin:1.3.1:exec)
|
||||
echo "Extracted 'main.adoc' from Maven build [${MAIN_ADOC_VALUE}]"
|
||||
|
||||
|
||||
WHITELIST_PROPERTY=${WHITELIST_PROPERTY:-"docs.whitelisted.branches"}
|
||||
WHITELISTED_BRANCHES_VALUE=$("${MAVEN_PATH}"mvn -q \
|
||||
-Dexec.executable="echo" \
|
||||
-Dexec.args="\${${WHITELIST_PROPERTY}}" \
|
||||
org.codehaus.mojo:exec-maven-plugin:1.3.1:exec \
|
||||
-P docs \
|
||||
-pl docs)
|
||||
echo "Extracted '${WHITELIST_PROPERTY}' from Maven build [${WHITELISTED_BRANCHES_VALUE}]"
|
||||
}
|
||||
|
||||
# Stash any outstanding changes
|
||||
###################################################################
|
||||
git diff-index --quiet HEAD
|
||||
dirty=$?
|
||||
if [ "$dirty" != "0" ]; then git stash; fi
|
||||
function stash_changes() {
|
||||
git diff-index --quiet HEAD && dirty=$? || (echo "Failed to check if the current repo is dirty. Assuming that it is." && dirty="1")
|
||||
if [ "$dirty" != "0" ]; then git stash; fi
|
||||
}
|
||||
|
||||
# Switch to gh-pages branch to sync it with current branch
|
||||
###################################################################
|
||||
git checkout gh-pages
|
||||
|
||||
for f in docs/target/generated-docs/*; do
|
||||
file=${f#docs/target/generated-docs/*}
|
||||
if ! git ls-files -i -o --exclude-standard --directory | grep -q ^$file$; then
|
||||
# Not ignored...
|
||||
cp -rf $f $target
|
||||
git add -A $target/$file
|
||||
function add_docs_from_target() {
|
||||
local DESTINATION_REPO_FOLDER
|
||||
if [[ -z "${DESTINATION}" && -z "${CLONE}" ]] ; then
|
||||
DESTINATION_REPO_FOLDER=${ROOT_FOLDER}
|
||||
elif [[ "${CLONE}" == "yes" ]]; then
|
||||
mkdir -p ${ROOT_FOLDER}/target
|
||||
local clonedStatic=${ROOT_FOLDER}/target/spring-cloud-static
|
||||
if [[ ! -e "${clonedStatic}/.git" ]]; then
|
||||
echo "Cloning Spring Cloud Static to target"
|
||||
git clone ${SPRING_CLOUD_STATIC_REPO} ${clonedStatic} && git checkout gh-pages
|
||||
else
|
||||
echo "Spring Cloud Static already cloned - will pull changes"
|
||||
cd ${clonedStatic} && git checkout gh-pages && git pull origin gh-pages
|
||||
fi
|
||||
DESTINATION_REPO_FOLDER=${clonedStatic}/${REPO_NAME}
|
||||
mkdir -p ${DESTINATION_REPO_FOLDER}
|
||||
else
|
||||
if [[ ! -e "${DESTINATION}/.git" ]]; then
|
||||
echo "[${DESTINATION}] is not a git repository"
|
||||
exit 1
|
||||
fi
|
||||
DESTINATION_REPO_FOLDER=${DESTINATION}/${REPO_NAME}
|
||||
mkdir -p ${DESTINATION_REPO_FOLDER}
|
||||
echo "Destination was provided [${DESTINATION}]"
|
||||
fi
|
||||
cd ${DESTINATION_REPO_FOLDER}
|
||||
git checkout gh-pages
|
||||
git pull origin gh-pages
|
||||
|
||||
# Add git branches
|
||||
###################################################################
|
||||
if [[ -z "${VERSION}" ]] ; then
|
||||
copy_docs_for_current_version
|
||||
else
|
||||
copy_docs_for_provided_version
|
||||
fi
|
||||
commit_changes_if_applicable
|
||||
}
|
||||
|
||||
|
||||
# Copies the docs by using the retrieved properties from Maven build
|
||||
function copy_docs_for_current_version() {
|
||||
if [[ "${CURRENT_BRANCH}" == "master" ]] ; then
|
||||
echo -e "Current branch is master - will copy the current docs only to the root folder"
|
||||
for f in docs/target/generated-docs/*; do
|
||||
file=${f#docs/target/generated-docs/*}
|
||||
if ! git ls-files -i -o --exclude-standard --directory | grep -q ^$file$; then
|
||||
# Not ignored...
|
||||
cp -rf $f ${ROOT_FOLDER}/
|
||||
git add -A ${ROOT_FOLDER}/$file
|
||||
fi
|
||||
done
|
||||
COMMIT_CHANGES="yes"
|
||||
else
|
||||
echo -e "Current branch is [${CURRENT_BRANCH}]"
|
||||
# http://stackoverflow.com/questions/29300806/a-bash-script-to-check-if-a-string-is-present-in-a-comma-separated-list-of-strin
|
||||
if [[ ",${WHITELISTED_BRANCHES_VALUE}," = *",${CURRENT_BRANCH},"* ]] ; then
|
||||
mkdir -p ${ROOT_FOLDER}/${CURRENT_BRANCH}
|
||||
echo -e "Branch [${CURRENT_BRANCH}] is whitelisted! Will copy the current docs to the [${CURRENT_BRANCH}] folder"
|
||||
for f in docs/target/generated-docs/*; do
|
||||
file=${f#docs/target/generated-docs/*}
|
||||
if ! git ls-files -i -o --exclude-standard --directory | grep -q ^$file$; then
|
||||
# Not ignored...
|
||||
# We want users to access 1.0.0.RELEASE/ instead of 1.0.0.RELEASE/spring-cloud.sleuth.html
|
||||
if [[ "${file}" == "${MAIN_ADOC_VALUE}.html" ]] ; then
|
||||
# We don't want to copy the spring-cloud-sleuth.html
|
||||
# we want it to be converted to index.html
|
||||
cp -rf $f ${ROOT_FOLDER}/${CURRENT_BRANCH}/index.html
|
||||
git add -A ${ROOT_FOLDER}/${CURRENT_BRANCH}/index.html
|
||||
else
|
||||
cp -rf $f ${ROOT_FOLDER}/${CURRENT_BRANCH}
|
||||
git add -A ${ROOT_FOLDER}/${CURRENT_BRANCH}/$file
|
||||
fi
|
||||
fi
|
||||
done
|
||||
COMMIT_CHANGES="yes"
|
||||
else
|
||||
echo -e "Branch [${CURRENT_BRANCH}] is not on the white list! Check out the Maven [${WHITELIST_PROPERTY}] property in
|
||||
[docs] module available under [docs] profile. Won't commit any changes to gh-pages for this branch."
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Copies the docs by using the explicitly provided version
|
||||
function copy_docs_for_provided_version() {
|
||||
local FOLDER=${DESTINATION_REPO_FOLDER}/${VERSION}
|
||||
mkdir -p ${FOLDER}
|
||||
echo -e "Current tag is [v${VERSION}] Will copy the current docs to the [${FOLDER}] folder"
|
||||
for f in ${ROOT_FOLDER}/docs/target/generated-docs/*; do
|
||||
file=${f#${ROOT_FOLDER}/docs/target/generated-docs/*}
|
||||
copy_docs_for_branch ${file} ${FOLDER}
|
||||
done
|
||||
COMMIT_CHANGES="yes"
|
||||
CURRENT_BRANCH="v${VERSION}"
|
||||
}
|
||||
|
||||
# Copies the docs from target to the provided destination
|
||||
# Params:
|
||||
# $1 - file from target
|
||||
# $2 - destination to which copy the files
|
||||
function copy_docs_for_branch() {
|
||||
local file=$1
|
||||
local destination=$2
|
||||
if ! git ls-files -i -o --exclude-standard --directory | grep -q ^${file}$; then
|
||||
# Not ignored...
|
||||
# We want users to access 1.0.0.RELEASE/ instead of 1.0.0.RELEASE/spring-cloud.sleuth.html
|
||||
if [[ ("${file}" == "${MAIN_ADOC_VALUE}.html") || ("${file}" == "${REPO_NAME}.html") ]] ; then
|
||||
# We don't want to copy the spring-cloud-sleuth.html
|
||||
# we want it to be converted to index.html
|
||||
cp -rf $f ${destination}/index.html
|
||||
git add -A ${destination}/index.html
|
||||
else
|
||||
cp -rf $f ${destination}
|
||||
git add -A ${destination}/$file
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
function commit_changes_if_applicable() {
|
||||
if [[ "${COMMIT_CHANGES}" == "yes" ]] ; then
|
||||
COMMIT_SUCCESSFUL="no"
|
||||
git commit -a -m "Sync docs from ${CURRENT_BRANCH} to gh-pages" && COMMIT_SUCCESSFUL="yes" || echo "Failed to commit changes"
|
||||
|
||||
# Uncomment the following push if you want to auto push to
|
||||
# the gh-pages branch whenever you commit to master locally.
|
||||
# This is a little extreme. Use with care!
|
||||
###################################################################
|
||||
if [[ "${COMMIT_SUCCESSFUL}" == "yes" ]] ; then
|
||||
git push origin gh-pages
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Switch back to the previous branch and exit block
|
||||
function checkout_previous_branch() {
|
||||
# If -version was provided we need to come back to root project
|
||||
cd ${ROOT_FOLDER}
|
||||
git checkout ${CURRENT_BRANCH} || echo "Failed to check the branch... continuing with the script"
|
||||
if [ "$dirty" != "0" ]; then git stash pop; fi
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Assert if properties have been properly passed
|
||||
function assert_properties() {
|
||||
echo "VERSION [${VERSION}], DESTINATION [${DESTINATION}], CLONE [${CLONE}]"
|
||||
if [[ "${VERSION}" != "" && (-z "${DESTINATION}" && -z "${CLONE}") ]] ; then echo "Version was set but destination / clone was not!"; exit 1;fi
|
||||
if [[ ("${DESTINATION}" != "" && "${CLONE}" != "") && -z "${VERSION}" ]] ; then echo "Destination / clone was set but version was not!"; exit 1;fi
|
||||
if [[ "${DESTINATION}" != "" && "${CLONE}" == "yes" ]] ; then echo "Destination and clone was set. Pick one!"; exit 1;fi
|
||||
}
|
||||
|
||||
# Prints the usage
|
||||
function print_usage() {
|
||||
cat <<EOF
|
||||
The idea of this script is to update gh-pages branch with the generated docs. Without any options
|
||||
the script will work in the following manner:
|
||||
|
||||
- if there's no gh-pages / target for docs module then the script ends
|
||||
- for master branch the generated docs are copied to the root of gh-pages branch
|
||||
- for any other branch (if that branch is whitelisted) a subfolder with branch name is created
|
||||
and docs are copied there
|
||||
- if the version switch is passed (-v) then a tag with (v) prefix will be retrieved and a folder
|
||||
with that version number will be created in the gh-pages branch. WARNING! No whitelist verification will take place
|
||||
- if the destination switch is passed (-d) then the script will check if the provided dir is a git repo and then will
|
||||
switch to gh-pages of that repo and copy the generated docs to `docs/<project-name>/<version>`
|
||||
- if the destination switch is passed (-d) then the script will check if the provided dir is a git repo and then will
|
||||
switch to gh-pages of that repo and copy the generated docs to `docs/<project-name>/<version>`
|
||||
|
||||
USAGE:
|
||||
|
||||
You can use the following options:
|
||||
|
||||
-v|--version - the script will apply the whole procedure for a particular library version
|
||||
-d|--destination - the root of destination folder where the docs should be copied. You have to use the full path.
|
||||
E.g. point to spring-cloud-static folder. Can't be used with (-c)
|
||||
-b|--build - will run the standard build process after checking out the branch
|
||||
-c|--clone - will automatically clone the spring-cloud-static repo instead of providing the destination.
|
||||
Obviously can't be used with (-d)
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
|
||||
# ==========================================
|
||||
# ____ ____ _____ _____ _____ _______
|
||||
# / ____|/ ____| __ \|_ _| __ \__ __|
|
||||
# | (___ | | | |__) | | | | |__) | | |
|
||||
# \___ \| | | _ / | | | ___/ | |
|
||||
# ____) | |____| | \ \ _| |_| | | |
|
||||
# |_____/ \_____|_| \_\_____|_| |_|
|
||||
#
|
||||
# ==========================================
|
||||
|
||||
while [[ $# > 0 ]]
|
||||
do
|
||||
key="$1"
|
||||
case ${key} in
|
||||
-v|--version)
|
||||
VERSION="$2"
|
||||
shift # past argument
|
||||
;;
|
||||
-d|--destination)
|
||||
DESTINATION="$2"
|
||||
shift # past argument
|
||||
;;
|
||||
-b|--build)
|
||||
BUILD="yes"
|
||||
;;
|
||||
-c|--clone)
|
||||
CLONE="yes"
|
||||
;;
|
||||
-h|--help)
|
||||
print_usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Invalid option: [$1]"
|
||||
print_usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
shift # past argument or value
|
||||
done
|
||||
|
||||
git add -A README.adoc || echo "No change to README.adoc"
|
||||
git commit -a -m "Sync docs from $branch to gh-pages" || echo "Nothing committed"
|
||||
|
||||
# Uncomment the following push if you want to auto push to
|
||||
# the gh-pages branch whenever you commit to branch locally.
|
||||
# This is a little extreme. Use with care!
|
||||
###################################################################
|
||||
git push origin gh-pages || echo "Cannot push gh-pages"
|
||||
|
||||
# Finally, switch back to the current branch and exit block
|
||||
git checkout $branch
|
||||
if [ "$dirty" != "0" ]; then git stash pop; fi
|
||||
|
||||
exit 0
|
||||
assert_properties
|
||||
set_default_props
|
||||
check_if_anything_to_sync
|
||||
if [[ -z "${VERSION}" ]] ; then
|
||||
retrieve_current_branch
|
||||
else
|
||||
switch_to_tag
|
||||
fi
|
||||
build_docs_if_applicable
|
||||
retrieve_doc_properties
|
||||
stash_changes
|
||||
add_docs_from_target
|
||||
checkout_previous_branch
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,30 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
base_dir = File.join(File.dirname(__FILE__),'../../..')
|
||||
src_dir = File.join(base_dir, "/src/main/asciidoc")
|
||||
require 'asciidoctor'
|
||||
require 'optparse'
|
||||
|
||||
options = {}
|
||||
file = "#{src_dir}/README.adoc"
|
||||
|
||||
OptionParser.new do |o|
|
||||
o.on('-o OUTPUT_FILE', 'Output file (default is stdout)') { |file| options[:to_file] = file unless file=='-' }
|
||||
o.on('-h', '--help') { puts o; exit }
|
||||
o.parse!
|
||||
end
|
||||
|
||||
file = ARGV[0] if ARGV.length>0
|
||||
|
||||
srcDir = File.dirname(file)
|
||||
out = "// Do not edit this file (e.g. go instead to docs/src/main/asciidoc)\n\n"
|
||||
doc = Asciidoctor.load_file file, safe: :safe, parse: false, attributes: 'allow-uri-read'
|
||||
out << doc.reader.read
|
||||
|
||||
unless options[:to_file]
|
||||
puts out
|
||||
else
|
||||
File.open(options[:to_file],'w+') do |file|
|
||||
file.write(out)
|
||||
end
|
||||
end
|
||||
@@ -46,15 +46,6 @@ if [ -z "$MAVEN_SKIP_RC" ] ; then
|
||||
|
||||
fi
|
||||
|
||||
VERSION=$(awk '/<parent/ {l=4}; (l-- > 0) {$0=$0} 1' `dirname $0`/pom.xml| grep '<version' | head -1 | sed -e 's/.*>\(.*\)<.*/\1/')
|
||||
if echo $VERSION | egrep -q 'M|RC'; then
|
||||
echo Activating \"milestone\" profile for version=\"$VERSION\"
|
||||
echo $MAVEN_ARGS | grep -q milestone || MAVEN_ARGS="$MAVEN_ARGS -Pmilestone"
|
||||
else
|
||||
echo Deactivating \"milestone\" profile for version=\"$VERSION\"
|
||||
echo $MAVEN_ARGS | grep -q milestone && MAVEN_ARGS=$(echo $MAVEN_ARGS | sed -e 's/-Pmilestone//')
|
||||
fi
|
||||
|
||||
# OS specific support. $var _must_ be set to either true or false.
|
||||
cygwin=false;
|
||||
darwin=false;
|
||||
@@ -66,27 +57,27 @@ case "`uname`" in
|
||||
#
|
||||
# Look for the Apple JDKs first to preserve the existing behaviour, and then look
|
||||
# for the new JDKs provided by Oracle.
|
||||
#
|
||||
#
|
||||
if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then
|
||||
#
|
||||
# Apple JDKs
|
||||
#
|
||||
export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home
|
||||
fi
|
||||
|
||||
|
||||
if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then
|
||||
#
|
||||
# Apple JDKs
|
||||
#
|
||||
export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home
|
||||
fi
|
||||
|
||||
|
||||
if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then
|
||||
#
|
||||
# Oracle JDKs
|
||||
#
|
||||
export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then
|
||||
#
|
||||
@@ -228,16 +219,35 @@ concat_lines() {
|
||||
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)}
|
||||
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
|
||||
|
||||
# Provide a "standardized" way to retrieve the CLI args that will
|
||||
# Provide a "standardized" way to retrieve the CLI args that will
|
||||
# work with both Windows and non-Windows executions.
|
||||
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
|
||||
export MAVEN_CMD_LINE_ARGS
|
||||
|
||||
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
|
||||
|
||||
echo "Running version check"
|
||||
VERSION=$( sed '\!<parent!,\!</parent!d' `dirname $0`/pom.xml | grep '<version' | head -1 | sed -e 's/.*<version>//' -e 's!</version>.*$!!' )
|
||||
echo "The found version is [${VERSION}]"
|
||||
|
||||
if echo $VERSION | egrep -q 'M|RC'; then
|
||||
echo Activating \"milestone\" profile for version=\"$VERSION\"
|
||||
echo $MAVEN_ARGS | grep -q milestone || MAVEN_ARGS="$MAVEN_ARGS -Pmilestone"
|
||||
else
|
||||
echo Deactivating \"milestone\" profile for version=\"$VERSION\"
|
||||
echo $MAVEN_ARGS | grep -q milestone && MAVEN_ARGS=$(echo $MAVEN_ARGS | sed -e 's/-Pmilestone//')
|
||||
fi
|
||||
|
||||
if echo $VERSION | egrep -q 'RELEASE'; then
|
||||
echo Activating \"central\" profile for version=\"$VERSION\"
|
||||
echo $MAVEN_ARGS | grep -q milestone || MAVEN_ARGS="$MAVEN_ARGS -Pcentral"
|
||||
else
|
||||
echo Deactivating \"central\" profile for version=\"$VERSION\"
|
||||
echo $MAVEN_ARGS | grep -q central && MAVEN_ARGS=$(echo $MAVEN_ARGS | sed -e 's/-Pcentral//')
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" \
|
||||
$MAVEN_OPTS \
|
||||
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
|
||||
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
|
||||
${WRAPPER_LAUNCHER} ${MAVEN_ARGS} "$@"
|
||||
|
||||
${WRAPPER_LAUNCHER} ${MAVEN_ARGS} "$@"
|
||||
@@ -1,145 +1,145 @@
|
||||
@REM ----------------------------------------------------------------------------
|
||||
@REM Licensed to the Apache Software Foundation (ASF) under one
|
||||
@REM or more contributor license agreements. See the NOTICE file
|
||||
@REM distributed with this work for additional information
|
||||
@REM regarding copyright ownership. The ASF licenses this file
|
||||
@REM to you under the Apache License, Version 2.0 (the
|
||||
@REM "License"); you may not use this file except in compliance
|
||||
@REM with the License. You may obtain a copy of the License at
|
||||
@REM
|
||||
@REM http://www.apache.org/licenses/LICENSE-2.0
|
||||
@REM
|
||||
@REM Unless required by applicable law or agreed to in writing,
|
||||
@REM software distributed under the License is distributed on an
|
||||
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
@REM KIND, either express or implied. See the License for the
|
||||
@REM specific language governing permissions and limitations
|
||||
@REM under the License.
|
||||
@REM ----------------------------------------------------------------------------
|
||||
|
||||
@REM ----------------------------------------------------------------------------
|
||||
@REM Maven2 Start Up Batch script
|
||||
@REM
|
||||
@REM Required ENV vars:
|
||||
@REM JAVA_HOME - location of a JDK home dir
|
||||
@REM
|
||||
@REM Optional ENV vars
|
||||
@REM M2_HOME - location of maven2's installed home dir
|
||||
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
|
||||
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
|
||||
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
|
||||
@REM e.g. to debug Maven itself, use
|
||||
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
|
||||
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
|
||||
@REM ----------------------------------------------------------------------------
|
||||
|
||||
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
|
||||
@echo off
|
||||
@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
|
||||
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
|
||||
|
||||
@REM set %HOME% to equivalent of $HOME
|
||||
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
|
||||
|
||||
@REM Execute a user defined script before this one
|
||||
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
|
||||
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
|
||||
if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
|
||||
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
|
||||
:skipRcPre
|
||||
|
||||
@setlocal
|
||||
|
||||
set ERROR_CODE=0
|
||||
|
||||
@REM To isolate internal variables from possible post scripts, we use another setlocal
|
||||
@setlocal
|
||||
|
||||
@REM ==== START VALIDATION ====
|
||||
if not "%JAVA_HOME%" == "" goto OkJHome
|
||||
|
||||
echo.
|
||||
echo Error: JAVA_HOME not found in your environment. >&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the >&2
|
||||
echo location of your Java installation. >&2
|
||||
echo.
|
||||
goto error
|
||||
|
||||
:OkJHome
|
||||
if exist "%JAVA_HOME%\bin\java.exe" goto init
|
||||
|
||||
echo.
|
||||
echo Error: JAVA_HOME is set to an invalid directory. >&2
|
||||
echo JAVA_HOME = "%JAVA_HOME%" >&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the >&2
|
||||
echo location of your Java installation. >&2
|
||||
echo.
|
||||
goto error
|
||||
|
||||
@REM ==== END VALIDATION ====
|
||||
|
||||
:init
|
||||
|
||||
set MAVEN_CMD_LINE_ARGS=%*
|
||||
|
||||
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
|
||||
@REM Fallback to current working directory if not found.
|
||||
|
||||
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
|
||||
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
|
||||
|
||||
set EXEC_DIR=%CD%
|
||||
set WDIR=%EXEC_DIR%
|
||||
:findBaseDir
|
||||
IF EXIST "%WDIR%"\.mvn goto baseDirFound
|
||||
cd ..
|
||||
IF "%WDIR%"=="%CD%" goto baseDirNotFound
|
||||
set WDIR=%CD%
|
||||
goto findBaseDir
|
||||
|
||||
:baseDirFound
|
||||
set MAVEN_PROJECTBASEDIR=%WDIR%
|
||||
cd "%EXEC_DIR%"
|
||||
goto endDetectBaseDir
|
||||
|
||||
:baseDirNotFound
|
||||
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
|
||||
cd "%EXEC_DIR%"
|
||||
|
||||
:endDetectBaseDir
|
||||
|
||||
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
|
||||
|
||||
@setlocal EnableExtensions EnableDelayedExpansion
|
||||
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
|
||||
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
|
||||
|
||||
:endReadAdditionalConfig
|
||||
|
||||
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
|
||||
@REM ----------------------------------------------------------------------------
|
||||
@REM Licensed to the Apache Software Foundation (ASF) under one
|
||||
@REM or more contributor license agreements. See the NOTICE file
|
||||
@REM distributed with this work for additional information
|
||||
@REM regarding copyright ownership. The ASF licenses this file
|
||||
@REM to you under the Apache License, Version 2.0 (the
|
||||
@REM "License"); you may not use this file except in compliance
|
||||
@REM with the License. You may obtain a copy of the License at
|
||||
@REM
|
||||
@REM http://www.apache.org/licenses/LICENSE-2.0
|
||||
@REM
|
||||
@REM Unless required by applicable law or agreed to in writing,
|
||||
@REM software distributed under the License is distributed on an
|
||||
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
@REM KIND, either express or implied. See the License for the
|
||||
@REM specific language governing permissions and limitations
|
||||
@REM under the License.
|
||||
@REM ----------------------------------------------------------------------------
|
||||
|
||||
@REM ----------------------------------------------------------------------------
|
||||
@REM Maven2 Start Up Batch script
|
||||
@REM
|
||||
@REM Required ENV vars:
|
||||
@REM JAVA_HOME - location of a JDK home dir
|
||||
@REM
|
||||
@REM Optional ENV vars
|
||||
@REM M2_HOME - location of maven2's installed home dir
|
||||
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
|
||||
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
|
||||
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
|
||||
@REM e.g. to debug Maven itself, use
|
||||
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
|
||||
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
|
||||
@REM ----------------------------------------------------------------------------
|
||||
|
||||
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
|
||||
@echo off
|
||||
@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
|
||||
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
|
||||
|
||||
@REM set %HOME% to equivalent of $HOME
|
||||
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
|
||||
|
||||
@REM Execute a user defined script before this one
|
||||
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
|
||||
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
|
||||
if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
|
||||
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
|
||||
:skipRcPre
|
||||
|
||||
@setlocal
|
||||
|
||||
set ERROR_CODE=0
|
||||
|
||||
@REM To isolate internal variables from possible post scripts, we use another setlocal
|
||||
@setlocal
|
||||
|
||||
@REM ==== START VALIDATION ====
|
||||
if not "%JAVA_HOME%" == "" goto OkJHome
|
||||
|
||||
echo.
|
||||
echo Error: JAVA_HOME not found in your environment. >&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the >&2
|
||||
echo location of your Java installation. >&2
|
||||
echo.
|
||||
goto error
|
||||
|
||||
:OkJHome
|
||||
if exist "%JAVA_HOME%\bin\java.exe" goto init
|
||||
|
||||
echo.
|
||||
echo Error: JAVA_HOME is set to an invalid directory. >&2
|
||||
echo JAVA_HOME = "%JAVA_HOME%" >&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the >&2
|
||||
echo location of your Java installation. >&2
|
||||
echo.
|
||||
goto error
|
||||
|
||||
@REM ==== END VALIDATION ====
|
||||
|
||||
:init
|
||||
|
||||
set MAVEN_CMD_LINE_ARGS=%*
|
||||
|
||||
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
|
||||
@REM Fallback to current working directory if not found.
|
||||
|
||||
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
|
||||
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
|
||||
|
||||
set EXEC_DIR=%CD%
|
||||
set WDIR=%EXEC_DIR%
|
||||
:findBaseDir
|
||||
IF EXIST "%WDIR%"\.mvn goto baseDirFound
|
||||
cd ..
|
||||
IF "%WDIR%"=="%CD%" goto baseDirNotFound
|
||||
set WDIR=%CD%
|
||||
goto findBaseDir
|
||||
|
||||
:baseDirFound
|
||||
set MAVEN_PROJECTBASEDIR=%WDIR%
|
||||
cd "%EXEC_DIR%"
|
||||
goto endDetectBaseDir
|
||||
|
||||
:baseDirNotFound
|
||||
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
|
||||
cd "%EXEC_DIR%"
|
||||
|
||||
:endDetectBaseDir
|
||||
|
||||
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
|
||||
|
||||
@setlocal EnableExtensions EnableDelayedExpansion
|
||||
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
|
||||
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
|
||||
|
||||
:endReadAdditionalConfig
|
||||
|
||||
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
|
||||
|
||||
set WRAPPER_JAR="".\.mvn\wrapper\maven-wrapper.jar""
|
||||
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
|
||||
|
||||
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS%
|
||||
if ERRORLEVEL 1 goto error
|
||||
goto end
|
||||
|
||||
:error
|
||||
set ERROR_CODE=1
|
||||
|
||||
:end
|
||||
@endlocal & set ERROR_CODE=%ERROR_CODE%
|
||||
|
||||
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
|
||||
@REM check for post script, once with legacy .bat ending and once with .cmd ending
|
||||
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
|
||||
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
|
||||
:skipRcPost
|
||||
|
||||
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
|
||||
if "%MAVEN_BATCH_PAUSE%" == "on" pause
|
||||
|
||||
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
|
||||
|
||||
exit /B %ERROR_CODE%
|
||||
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
|
||||
|
||||
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS%
|
||||
if ERRORLEVEL 1 goto error
|
||||
goto end
|
||||
|
||||
:error
|
||||
set ERROR_CODE=1
|
||||
|
||||
:end
|
||||
@endlocal & set ERROR_CODE=%ERROR_CODE%
|
||||
|
||||
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
|
||||
@REM check for post script, once with legacy .bat ending and once with .cmd ending
|
||||
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
|
||||
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
|
||||
:skipRcPost
|
||||
|
||||
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
|
||||
if "%MAVEN_BATCH_PAUSE%" == "on" pause
|
||||
|
||||
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
|
||||
|
||||
exit /B %ERROR_CODE%
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-build</artifactId>
|
||||
<version>1.1.1.RELEASE</version>
|
||||
<relativePath />
|
||||
</parent>
|
||||
<artifactId>spring-cloud-netflix</artifactId>
|
||||
<version>1.1.3.BUILD-SNAPSHOT</version>
|
||||
<version>1.4.1.RELEASE</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>Spring Cloud Netflix</name>
|
||||
<description>Spring Cloud Netflix</description>
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-build</artifactId>
|
||||
<version>1.3.7.RELEASE</version>
|
||||
<relativePath />
|
||||
</parent>
|
||||
<scm>
|
||||
<url>https://github.com/spring-cloud/spring-cloud-netflix</url>
|
||||
<connection>scm:git:git://github.com/spring-cloud/spring-cloud-netflix.git</connection>
|
||||
@@ -22,9 +22,17 @@
|
||||
<properties>
|
||||
<bintray.package>netflix</bintray.package>
|
||||
<main.basedir>${basedir}</main.basedir>
|
||||
<netty.version>4.0.27.Final</netty.version>
|
||||
<netty.version>4.0.27.Final</netty.version>
|
||||
<jackson.version>2.7.3</jackson.version>
|
||||
<spring-cloud-commons.version>1.3.1.RELEASE</spring-cloud-commons.version>
|
||||
<spring-cloud-config.version>1.4.1.RELEASE</spring-cloud-config.version>
|
||||
<spring-cloud-stream.version>Ditmars.SR3</spring-cloud-stream.version>
|
||||
<!-- Has to be a stable version (not one that depends on this version of netflix): -->
|
||||
<donotreplacespring-cloud-contract.version>1.1.2.RELEASE</donotreplacespring-cloud-contract.version>
|
||||
|
||||
<!-- Plugin versions -->
|
||||
<maven-compiler-plugin.version>3.6.1</maven-compiler-plugin.version>
|
||||
<maven-eclipse-plugin.version>2.10</maven-eclipse-plugin.version>
|
||||
<!-- Sonar -->
|
||||
<surefire.plugin.version>2.19.1</surefire.plugin.version>
|
||||
<sonar.java.coveragePlugin>jacoco</sonar.java.coveragePlugin>
|
||||
@@ -37,6 +45,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-eclipse-plugin</artifactId>
|
||||
<version>${maven-eclipse-plugin.version}</version>
|
||||
<configuration>
|
||||
<useProjectReferences>false</useProjectReferences>
|
||||
<additionalConfig>
|
||||
@@ -53,6 +62,7 @@
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>${maven-compiler-plugin.version}</version>
|
||||
<configuration>
|
||||
<source>1.7</source>
|
||||
<target>1.7</target>
|
||||
@@ -62,6 +72,12 @@
|
||||
</build>
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-netflix-hystrix-contract</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-netflix-dependencies</artifactId>
|
||||
@@ -69,6 +85,41 @@
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-commons-dependencies</artifactId>
|
||||
<version>${spring-cloud-commons.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-commons</artifactId>
|
||||
<type>test-jar</type>
|
||||
<scope>test</scope>
|
||||
<version>${spring-cloud-commons.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-config-dependencies</artifactId>
|
||||
<version>${spring-cloud-config.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream-dependencies</artifactId>
|
||||
<version>${spring-cloud-stream.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-contract-dependencies</artifactId>
|
||||
<version>${donotreplacespring-cloud-contract.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-codec-http</artifactId>
|
||||
@@ -88,6 +139,8 @@
|
||||
</dependencyManagement>
|
||||
<modules>
|
||||
<module>spring-cloud-netflix-dependencies</module>
|
||||
<!-- Not part of the reactor build: build and deploy separately -->
|
||||
<!--module>spring-cloud-netflix-hystrix-contract</module-->
|
||||
<module>spring-cloud-netflix-core</module>
|
||||
<module>spring-cloud-netflix-hystrix-dashboard</module>
|
||||
<module>spring-cloud-netflix-hystrix-amqp</module>
|
||||
@@ -98,6 +151,7 @@
|
||||
<module>spring-cloud-netflix-turbine</module>
|
||||
<module>spring-cloud-netflix-turbine-stream</module>
|
||||
<module>spring-cloud-netflix-sidecar</module>
|
||||
<module>spring-cloud-starter-netflix</module>
|
||||
<module>spring-cloud-starter-archaius</module>
|
||||
<module>spring-cloud-starter-atlas</module>
|
||||
<module>spring-cloud-starter-eureka</module>
|
||||
@@ -120,7 +174,7 @@
|
||||
<repository>
|
||||
<id>spring-snapshots</id>
|
||||
<name>Spring Snapshots</name>
|
||||
<url>http://repo.spring.io/libs-snapshot-local</url>
|
||||
<url>https://repo.spring.io/libs-snapshot-local</url>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
@@ -131,7 +185,7 @@
|
||||
<repository>
|
||||
<id>spring-milestones</id>
|
||||
<name>Spring Milestones</name>
|
||||
<url>http://repo.spring.io/libs-milestone-local</url>
|
||||
<url>https://repo.spring.io/libs-milestone-local</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
@@ -139,7 +193,7 @@
|
||||
<repository>
|
||||
<id>spring-releases</id>
|
||||
<name>Spring Releases</name>
|
||||
<url>http://repo.spring.io/release</url>
|
||||
<url>https://repo.spring.io/release</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
@@ -149,7 +203,7 @@
|
||||
<pluginRepository>
|
||||
<id>spring-snapshots</id>
|
||||
<name>Spring Snapshots</name>
|
||||
<url>http://repo.spring.io/libs-snapshot-local</url>
|
||||
<url>https://repo.spring.io/libs-snapshot-local</url>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
@@ -160,7 +214,7 @@
|
||||
<pluginRepository>
|
||||
<id>spring-milestones</id>
|
||||
<name>Spring Milestones</name>
|
||||
<url>http://repo.spring.io/libs-milestone-local</url>
|
||||
<url>https://repo.spring.io/libs-milestone-local</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
@@ -168,7 +222,7 @@
|
||||
<pluginRepository>
|
||||
<id>spring-releases</id>
|
||||
<name>Spring Releases</name>
|
||||
<url>http://repo.spring.io/libs-release-local</url>
|
||||
<url>https://repo.spring.io/libs-release-local</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
@@ -177,52 +231,45 @@
|
||||
</profile>
|
||||
<profile>
|
||||
<id>sonar</id>
|
||||
<activation>
|
||||
<activeByDefault>false</activeByDefault>
|
||||
</activation>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
<version>0.7.4.201502262128</version>
|
||||
<configuration>
|
||||
<destFile>${sonar.jacoco.reportPath}</destFile>
|
||||
<append>true</append>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>agent</id>
|
||||
<id>pre-unit-test</id>
|
||||
<goals>
|
||||
<goal>prepare-agent</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<propertyName>surefireArgLine</propertyName>
|
||||
<destFile>${project.build.directory}/jacoco.exec</destFile>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>post-unit-test</id>
|
||||
<phase>test</phase>
|
||||
<goals>
|
||||
<goal>report</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<!-- Sets the path to the file which contains the execution data. -->
|
||||
<dataFile>${project.build.directory}/jacoco.exec</dataFile>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<!-- Minimal supported version is 2.4 -->
|
||||
<version>${surefire.plugin.version}</version>
|
||||
<configuration>
|
||||
<properties>
|
||||
<property>
|
||||
<name>listener</name>
|
||||
<value>org.sonar.java.jacoco.JUnitListener</value>
|
||||
</property>
|
||||
</properties>
|
||||
<!-- Sets the VM argument line used when unit tests are run. -->
|
||||
<argLine>${surefireArgLine}</argLine>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.sonarsource.java</groupId>
|
||||
<artifactId>sonar-jacoco-listeners</artifactId>
|
||||
<version>3.8</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
(cd spring-cloud-netflix-hystrix-contract && ../mvnw clean install -B -Pdocs ${@})
|
||||
./mvnw clean install -B -Pdocs ${@}
|
||||
@@ -14,9 +14,9 @@ curl "${SCRIPT_URL}" --output runAcceptanceTests.sh
|
||||
chmod +x runAcceptanceTests.sh
|
||||
|
||||
echo "Killing all running apps"
|
||||
./runAcceptanceTests.sh -t "${AT_WHAT_TO_TEST}" -n
|
||||
./runAcceptanceTests.sh -t "${AT_WHAT_TO_TEST}" --killnow
|
||||
|
||||
./runAcceptanceTests.sh -t "${AT_WHAT_TO_TEST}" -k
|
||||
./runAcceptanceTests.sh -t "${AT_WHAT_TO_TEST}" --killattheend
|
||||
|
||||
SCRIPT_URL="https://raw.githubusercontent.com/spring-cloud-samples/tests/master/scripts/runTests.sh"
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-netflix</artifactId>
|
||||
<version>1.1.3.BUILD-SNAPSHOT</version>
|
||||
<version>1.4.1.RELEASE</version>
|
||||
<relativePath>..</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<artifactId>spring-cloud-netflix-core</artifactId>
|
||||
@@ -34,11 +34,26 @@
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.retry</groupId>
|
||||
<artifactId>spring-retry</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-commons</artifactId>
|
||||
@@ -82,27 +97,27 @@
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.netflix.feign</groupId>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-core</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.netflix.feign</groupId>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-slf4j</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.netflix.feign</groupId>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-httpclient</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.netflix.feign</groupId>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-hystrix</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.netflix.feign</groupId>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-okhttp</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
@@ -162,30 +177,19 @@
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp</groupId>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<!-- Only needed at compile time -->
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-jdbc</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-commons</artifactId>
|
||||
<type>test-jar</type>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
||||
+36
-26
@@ -17,30 +17,12 @@
|
||||
package org.springframework.cloud.netflix.archaius;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import javax.annotation.PreDestroy;
|
||||
|
||||
import org.apache.commons.configuration.AbstractConfiguration;
|
||||
import org.apache.commons.configuration.ConfigurationBuilder;
|
||||
import org.apache.commons.configuration.EnvironmentConfiguration;
|
||||
import org.apache.commons.configuration.SystemConfiguration;
|
||||
import org.apache.commons.configuration.event.ConfigurationEvent;
|
||||
import org.apache.commons.configuration.event.ConfigurationListener;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.actuate.condition.ConditionalOnEnabledEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.Endpoint;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import com.netflix.config.AggregatedConfiguration;
|
||||
import com.netflix.config.ConcurrentCompositeConfiguration;
|
||||
import com.netflix.config.ConfigurationManager;
|
||||
@@ -49,6 +31,30 @@ import com.netflix.config.DynamicProperty;
|
||||
import com.netflix.config.DynamicPropertyFactory;
|
||||
import com.netflix.config.DynamicURLConfiguration;
|
||||
|
||||
import org.apache.commons.configuration.AbstractConfiguration;
|
||||
import org.apache.commons.configuration.ConfigurationBuilder;
|
||||
import org.apache.commons.configuration.EnvironmentConfiguration;
|
||||
import org.apache.commons.configuration.SystemConfiguration;
|
||||
import org.apache.commons.configuration.event.ConfigurationEvent;
|
||||
import org.apache.commons.configuration.event.ConfigurationListener;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.actuate.condition.ConditionalOnEnabledEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.Endpoint;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static com.netflix.config.ConfigurationManager.APPLICATION_PROPERTIES;
|
||||
import static com.netflix.config.ConfigurationManager.DISABLE_DEFAULT_ENV_CONFIG;
|
||||
import static com.netflix.config.ConfigurationManager.DISABLE_DEFAULT_SYS_CONFIG;
|
||||
@@ -56,27 +62,32 @@ import static com.netflix.config.ConfigurationManager.ENV_CONFIG_NAME;
|
||||
import static com.netflix.config.ConfigurationManager.SYS_CONFIG_NAME;
|
||||
import static com.netflix.config.ConfigurationManager.URL_CONFIG_NAME;
|
||||
|
||||
import lombok.extern.apachecommons.CommonsLog;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnClass({ ConcurrentCompositeConfiguration.class,
|
||||
ConfigurationBuilder.class })
|
||||
@CommonsLog
|
||||
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
|
||||
public class ArchaiusAutoConfiguration {
|
||||
|
||||
private static final Log log = LogFactory.getLog(ArchaiusAutoConfiguration.class);
|
||||
|
||||
private static final AtomicBoolean initialized = new AtomicBoolean(false);
|
||||
|
||||
@Autowired
|
||||
private ConfigurableEnvironment env;
|
||||
|
||||
@Autowired
|
||||
private List<AbstractConfiguration> externalConfigurations;
|
||||
@Autowired(required = false)
|
||||
private List<AbstractConfiguration> externalConfigurations = new ArrayList<>();
|
||||
|
||||
private DynamicURLConfiguration defaultURLConfig;
|
||||
|
||||
@PreDestroy
|
||||
public void close() {
|
||||
if (defaultURLConfig != null) {
|
||||
defaultURLConfig.stopLoading();
|
||||
}
|
||||
setStatic(ConfigurationManager.class, "instance", null);
|
||||
setStatic(ConfigurationManager.class, "customConfigurationInstalled", false);
|
||||
setStatic(DynamicPropertyFactory.class, "config", null);
|
||||
@@ -150,8 +161,7 @@ public class ArchaiusAutoConfiguration {
|
||||
config.addConfiguration(envConfig,
|
||||
ConfigurableEnvironmentConfiguration.class.getSimpleName());
|
||||
|
||||
// below come from ConfigurationManager.createDefaultConfigInstance()
|
||||
DynamicURLConfiguration defaultURLConfig = new DynamicURLConfiguration();
|
||||
defaultURLConfig = new DynamicURLConfiguration();
|
||||
try {
|
||||
config.addConfiguration(defaultURLConfig, URL_CONFIG_NAME);
|
||||
}
|
||||
|
||||
+6
-4
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.cloud.netflix.endpoint;
|
||||
|
||||
import javax.servlet.Servlet;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
@@ -60,8 +61,8 @@ public abstract class ServletWrappingEndpoint implements InitializingBean,
|
||||
this.controller.setApplicationContext(applicationContext);
|
||||
}
|
||||
|
||||
protected ServletWrappingEndpoint(Class<?> servletClass, String servletName,
|
||||
String path, boolean sensitive, boolean enabled) {
|
||||
protected ServletWrappingEndpoint(Class<? extends Servlet> servletClass,
|
||||
String servletName, String path, boolean sensitive, boolean enabled) {
|
||||
this.controller.setServletClass(servletClass);
|
||||
this.controller.setServletName(servletName);
|
||||
this.path = path;
|
||||
@@ -76,12 +77,13 @@ public abstract class ServletWrappingEndpoint implements InitializingBean,
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
public ServletWrappingController getController() {
|
||||
return controller;
|
||||
return this.controller;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPath() {
|
||||
return this.path;
|
||||
|
||||
+6
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.cloud.netflix.feign;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
|
||||
import feign.MethodMetadata;
|
||||
@@ -25,6 +26,7 @@ import feign.MethodMetadata;
|
||||
* Feign contract method parameter processor.
|
||||
*
|
||||
* @author Jakub Narloch
|
||||
* @author Abhijit Sarkar
|
||||
*/
|
||||
public interface AnnotatedParameterProcessor {
|
||||
|
||||
@@ -38,11 +40,12 @@ public interface AnnotatedParameterProcessor {
|
||||
/**
|
||||
* Process the annotated parameter.
|
||||
*
|
||||
* @param context the parameter context
|
||||
* @param context the parameter context
|
||||
* @param annotation the annotation instance
|
||||
* @param method the method that contains the annotation
|
||||
* @return whether the parameter is http
|
||||
*/
|
||||
boolean processArgument(AnnotatedParameterContext context, Annotation annotation);
|
||||
boolean processArgument(AnnotatedParameterContext context, Annotation annotation, Method method);
|
||||
|
||||
/**
|
||||
* Specifies the parameter context.
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.netflix.feign;
|
||||
|
||||
import feign.Logger;
|
||||
import feign.slf4j.Slf4jLogger;
|
||||
|
||||
/**
|
||||
* @author Venil Noronha
|
||||
*/
|
||||
public class DefaultFeignLoggerFactory implements FeignLoggerFactory {
|
||||
|
||||
private Logger logger;
|
||||
|
||||
public DefaultFeignLoggerFactory(Logger logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Logger create(Class<?> type) {
|
||||
return this.logger != null ? this.logger : new Slf4jLogger(type);
|
||||
}
|
||||
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2013-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.netflix.feign;
|
||||
|
||||
import feign.Feign;
|
||||
import feign.Target;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
class DefaultTargeter implements Targeter {
|
||||
|
||||
@Override
|
||||
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
|
||||
Target.HardCodedTarget<T> target) {
|
||||
return feign.target(target);
|
||||
}
|
||||
}
|
||||
+158
@@ -18,14 +18,37 @@ package org.springframework.cloud.netflix.feign;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.config.RegistryBuilder;
|
||||
import org.apache.http.conn.HttpClientConnectionManager;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.client.actuator.HasFeatures;
|
||||
import org.springframework.cloud.commons.httpclient.ApacheHttpClientConnectionManagerFactory;
|
||||
import org.springframework.cloud.commons.httpclient.ApacheHttpClientFactory;
|
||||
import org.springframework.cloud.commons.httpclient.OkHttpClientConnectionPoolFactory;
|
||||
import org.springframework.cloud.commons.httpclient.OkHttpClientFactory;
|
||||
import org.springframework.cloud.netflix.feign.support.FeignHttpClientProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import feign.Client;
|
||||
import feign.Feign;
|
||||
import feign.httpclient.ApacheHttpClient;
|
||||
import feign.okhttp.OkHttpClient;
|
||||
import okhttp3.ConnectionPool;
|
||||
|
||||
import javax.annotation.PreDestroy;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
@@ -33,6 +56,7 @@ import feign.Feign;
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnClass(Feign.class)
|
||||
@EnableConfigurationProperties({FeignClientProperties.class, FeignHttpClientProperties.class})
|
||||
public class FeignAutoConfiguration {
|
||||
|
||||
@Autowired(required = false)
|
||||
@@ -49,4 +73,138 @@ public class FeignAutoConfiguration {
|
||||
context.setConfigurations(this.configurations);
|
||||
return context;
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnClass(name = "feign.hystrix.HystrixFeign")
|
||||
protected static class HystrixFeignTargeterConfiguration {
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public Targeter feignTargeter() {
|
||||
return new HystrixTargeter();
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
|
||||
protected static class DefaultFeignTargeterConfiguration {
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public Targeter feignTargeter() {
|
||||
return new DefaultTargeter();
|
||||
}
|
||||
}
|
||||
|
||||
// the following configuration is for alternate feign clients if
|
||||
// ribbon is not on the class path.
|
||||
// see corresponding configurations in FeignRibbonClientAutoConfiguration
|
||||
// for load balanced ribbon clients.
|
||||
@Configuration
|
||||
@ConditionalOnClass(ApacheHttpClient.class)
|
||||
@ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
|
||||
@ConditionalOnMissingBean(CloseableHttpClient.class)
|
||||
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
|
||||
protected static class HttpClientFeignConfiguration {
|
||||
private final Timer connectionManagerTimer = new Timer(
|
||||
"FeignApacheHttpClientConfiguration.connectionManagerTimer", true);
|
||||
|
||||
@Autowired(required = false)
|
||||
private RegistryBuilder registryBuilder;
|
||||
|
||||
private CloseableHttpClient httpClient;
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(HttpClientConnectionManager.class)
|
||||
public HttpClientConnectionManager connectionManager(
|
||||
ApacheHttpClientConnectionManagerFactory connectionManagerFactory,
|
||||
FeignHttpClientProperties httpClientProperties) {
|
||||
final HttpClientConnectionManager connectionManager = connectionManagerFactory
|
||||
.newConnectionManager(false, httpClientProperties.getMaxConnections(),
|
||||
httpClientProperties.getMaxConnectionsPerRoute(),
|
||||
httpClientProperties.getTimeToLive(),
|
||||
httpClientProperties.getTimeToLiveUnit(), registryBuilder);
|
||||
this.connectionManagerTimer.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
connectionManager.closeExpiredConnections();
|
||||
}
|
||||
}, 30000, httpClientProperties.getConnectionTimerRepeat());
|
||||
return connectionManager;
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
public CloseableHttpClient httpClient(ApacheHttpClientFactory httpClientFactory,
|
||||
HttpClientConnectionManager httpClientConnectionManager,
|
||||
FeignHttpClientProperties httpClientProperties) {
|
||||
RequestConfig defaultRequestConfig = RequestConfig.custom()
|
||||
.setConnectTimeout(httpClientProperties.getConnectionTimeout())
|
||||
.setRedirectsEnabled(httpClientProperties.isFollowRedirects())
|
||||
.build();
|
||||
this.httpClient = httpClientFactory.createBuilder().
|
||||
setConnectionManager(httpClientConnectionManager).
|
||||
setDefaultRequestConfig(defaultRequestConfig).build();
|
||||
return this.httpClient;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(Client.class)
|
||||
public Client feignClient(HttpClient httpClient) {
|
||||
return new ApacheHttpClient(httpClient);
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void destroy() throws Exception {
|
||||
connectionManagerTimer.cancel();
|
||||
if(httpClient != null) {
|
||||
httpClient.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnClass(OkHttpClient.class)
|
||||
@ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
|
||||
@ConditionalOnMissingBean(okhttp3.OkHttpClient.class)
|
||||
@ConditionalOnProperty(value = "feign.okhttp.enabled")
|
||||
protected static class OkHttpFeignConfiguration {
|
||||
|
||||
private okhttp3.OkHttpClient okHttpClient;
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(ConnectionPool.class)
|
||||
public ConnectionPool httpClientConnectionPool(FeignHttpClientProperties httpClientProperties,
|
||||
OkHttpClientConnectionPoolFactory connectionPoolFactory) {
|
||||
Integer maxTotalConnections = httpClientProperties.getMaxConnections();
|
||||
Long timeToLive = httpClientProperties.getTimeToLive();
|
||||
TimeUnit ttlUnit = httpClientProperties.getTimeToLiveUnit();
|
||||
return connectionPoolFactory.create(maxTotalConnections, timeToLive, ttlUnit);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public okhttp3.OkHttpClient client(OkHttpClientFactory httpClientFactory,
|
||||
ConnectionPool connectionPool, FeignHttpClientProperties httpClientProperties) {
|
||||
Boolean followRedirects = httpClientProperties.isFollowRedirects();
|
||||
Integer connectTimeout = httpClientProperties.getConnectionTimeout();
|
||||
this.okHttpClient = httpClientFactory.createBuilder(false).
|
||||
connectTimeout(connectTimeout, TimeUnit.MILLISECONDS).
|
||||
followRedirects(followRedirects).
|
||||
connectionPool(connectionPool).build();
|
||||
return this.okHttpClient;
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void destroy() {
|
||||
if(okHttpClient != null) {
|
||||
okHttpClient.dispatcher().executorService().shutdown();
|
||||
okHttpClient.connectionPool().evictAll();
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(Client.class)
|
||||
public Client feignClient() {
|
||||
return new OkHttpClient(this.okHttpClient);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+20
@@ -59,6 +59,11 @@ public @interface FeignClient {
|
||||
*/
|
||||
@AliasFor("value")
|
||||
String name() default "";
|
||||
|
||||
/**
|
||||
* Sets the <code>@Qualifier</code> value for the feign client.
|
||||
*/
|
||||
String qualifier() default "";
|
||||
|
||||
/**
|
||||
* An absolute URL or resolvable hostname (the protocol is optional).
|
||||
@@ -85,10 +90,25 @@ public @interface FeignClient {
|
||||
*/
|
||||
Class<?> fallback() default void.class;
|
||||
|
||||
/**
|
||||
* Define a fallback factory for the specified Feign client interface. The fallback
|
||||
* factory must produce instances of fallback classes that implement the interface
|
||||
* annotated by {@link FeignClient}. The fallback factory must be a valid spring
|
||||
* bean.
|
||||
*
|
||||
* @see feign.hystrix.FallbackFactory for details.
|
||||
*/
|
||||
Class<?> fallbackFactory() default void.class;
|
||||
|
||||
/**
|
||||
* Path prefix to be used by all method-level mappings. Can be used with or without
|
||||
* <code>@RibbonClient</code>.
|
||||
*/
|
||||
String path() default "";
|
||||
|
||||
/**
|
||||
* Whether to mark the feign proxy as a primary bean. Defaults to true.
|
||||
*/
|
||||
boolean primary() default true;
|
||||
|
||||
}
|
||||
|
||||
+179
-63
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2016 the original author or authors.
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,14 +17,17 @@
|
||||
package org.springframework.cloud.netflix.feign;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.cloud.netflix.feign.ribbon.LoadBalancerFeignClient;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import feign.Client;
|
||||
@@ -38,32 +41,18 @@ import feign.Target.HardCodedTarget;
|
||||
import feign.codec.Decoder;
|
||||
import feign.codec.Encoder;
|
||||
import feign.codec.ErrorDecoder;
|
||||
import feign.slf4j.Slf4jLogger;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
* @author Venil Noronha
|
||||
* @author Eko Kurniawan Khannedy
|
||||
* @author Gregor Zurowski
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean,
|
||||
ApplicationContextAware {
|
||||
|
||||
private static final Targeter targeter;
|
||||
|
||||
static {
|
||||
Targeter targeterToUse;
|
||||
if (ClassUtils.isPresent("feign.hystrix.HystrixFeign",
|
||||
FeignClientFactoryBean.class.getClassLoader())) {
|
||||
targeterToUse = new HystrixTargeter();
|
||||
}
|
||||
else {
|
||||
targeterToUse = new DefaultTargeter();
|
||||
}
|
||||
targeter = targeterToUse;
|
||||
}
|
||||
/***********************************
|
||||
* WARNING! Nothing in this class should be @Autowired. It causes NPEs because of some lifecycle race condition.
|
||||
***********************************/
|
||||
|
||||
private Class<?> type;
|
||||
|
||||
@@ -79,6 +68,8 @@ class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean,
|
||||
|
||||
private Class<?> fallback = void.class;
|
||||
|
||||
private Class<?> fallbackFactory = void.class;
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
Assert.hasText(this.name, "Name must be set");
|
||||
@@ -90,11 +81,8 @@ class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean,
|
||||
}
|
||||
|
||||
protected Feign.Builder feign(FeignContext context) {
|
||||
Logger logger = getOptional(context, Logger.class);
|
||||
|
||||
if (logger == null) {
|
||||
logger = new Slf4jLogger(this.type);
|
||||
}
|
||||
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
|
||||
Logger logger = loggerFactory.create(this.type);
|
||||
|
||||
// @formatter:off
|
||||
Feign.Builder builder = get(context, Feign.Builder.class)
|
||||
@@ -105,7 +93,29 @@ class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean,
|
||||
.contract(get(context, Contract.class));
|
||||
// @formatter:on
|
||||
|
||||
// optional values
|
||||
configureFeign(context, builder);
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
protected void configureFeign(FeignContext context, Feign.Builder builder) {
|
||||
FeignClientProperties properties = applicationContext.getBean(FeignClientProperties.class);
|
||||
if (properties != null) {
|
||||
if (properties.isDefaultToProperties()) {
|
||||
configureUsingConfiguration(context, builder);
|
||||
configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
|
||||
configureUsingProperties(properties.getConfig().get(this.name), builder);
|
||||
} else {
|
||||
configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
|
||||
configureUsingProperties(properties.getConfig().get(this.name), builder);
|
||||
configureUsingConfiguration(context, builder);
|
||||
}
|
||||
} else {
|
||||
configureUsingConfiguration(context, builder);
|
||||
}
|
||||
}
|
||||
|
||||
protected void configureUsingConfiguration(FeignContext context, Feign.Builder builder) {
|
||||
Logger.Level level = getOptional(context, Logger.Level.class);
|
||||
if (level != null) {
|
||||
builder.logLevel(level);
|
||||
@@ -131,8 +141,52 @@ class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean,
|
||||
if (decode404) {
|
||||
builder.decode404();
|
||||
}
|
||||
}
|
||||
|
||||
return builder;
|
||||
protected void configureUsingProperties(FeignClientProperties.FeignClientConfiguration config, Feign.Builder builder) {
|
||||
if (config == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (config.getLoggerLevel() != null) {
|
||||
builder.logLevel(config.getLoggerLevel());
|
||||
}
|
||||
|
||||
if (config.getConnectTimeout() != null && config.getReadTimeout() != null) {
|
||||
builder.options(new Request.Options(config.getConnectTimeout(), config.getReadTimeout()));
|
||||
}
|
||||
|
||||
if (config.getRetryer() != null) {
|
||||
Retryer retryer = getOrInstantiate(config.getRetryer());
|
||||
builder.retryer(retryer);
|
||||
}
|
||||
|
||||
if (config.getErrorDecoder() != null) {
|
||||
ErrorDecoder errorDecoder = getOrInstantiate(config.getErrorDecoder());
|
||||
builder.errorDecoder(errorDecoder);
|
||||
}
|
||||
|
||||
if (config.getRequestInterceptors() != null && !config.getRequestInterceptors().isEmpty()) {
|
||||
// this will add request interceptor to builder, not replace existing
|
||||
for (Class<RequestInterceptor> bean : config.getRequestInterceptors()) {
|
||||
RequestInterceptor interceptor = getOrInstantiate(bean);
|
||||
builder.requestInterceptor(interceptor);
|
||||
}
|
||||
}
|
||||
|
||||
if (config.getDecode404() != null) {
|
||||
if (config.getDecode404()) {
|
||||
builder.decode404();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private <T> T getOrInstantiate(Class<T> tClass) {
|
||||
try {
|
||||
return applicationContext.getBean(tClass);
|
||||
} catch (NoSuchBeanDefinitionException e) {
|
||||
return BeanUtils.instantiateClass(tClass);
|
||||
}
|
||||
}
|
||||
|
||||
protected <T> T get(FeignContext context, Class<T> type) {
|
||||
@@ -153,11 +207,12 @@ class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean,
|
||||
Client client = getOptional(context, Client.class);
|
||||
if (client != null) {
|
||||
builder.client(client);
|
||||
Targeter targeter = get(context, Targeter.class);
|
||||
return targeter.target(this, builder, context, target);
|
||||
}
|
||||
|
||||
throw new IllegalStateException(
|
||||
"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-ribbon?");
|
||||
"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -181,6 +236,16 @@ class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean,
|
||||
this.url = "http://" + this.url;
|
||||
}
|
||||
String url = this.url + cleanPath();
|
||||
Client client = getOptional(context, Client.class);
|
||||
if (client != null) {
|
||||
if (client instanceof LoadBalancerFeignClient) {
|
||||
// not lod balancing because we have a url,
|
||||
// but ribbon is on the classpath, so unwrap
|
||||
client = ((LoadBalancerFeignClient)client).getDelegate();
|
||||
}
|
||||
builder.client(client);
|
||||
}
|
||||
Targeter targeter = get(context, Targeter.class);
|
||||
return targeter.target(this, builder, context, new HardCodedTarget<>(
|
||||
this.type, this.name, url));
|
||||
}
|
||||
@@ -208,48 +273,99 @@ class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean,
|
||||
return true;
|
||||
}
|
||||
|
||||
interface Targeter {
|
||||
<T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
|
||||
HardCodedTarget<T> target);
|
||||
public Class<?> getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
static class DefaultTargeter implements Targeter {
|
||||
|
||||
@Override
|
||||
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
|
||||
HardCodedTarget<T> target) {
|
||||
return feign.target(target);
|
||||
}
|
||||
public void setType(Class<?> type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static class HystrixTargeter implements Targeter {
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
|
||||
HardCodedTarget<T> target) {
|
||||
if (factory.fallback == void.class
|
||||
|| !(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
|
||||
return feign.target(target);
|
||||
}
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
Object fallbackInstance = context.getInstance(factory.name, factory.fallback);
|
||||
if (fallbackInstance == null) {
|
||||
throw new IllegalStateException(String.format(
|
||||
"No fallback instance of type %s found for feign client %s",
|
||||
factory.fallback, factory.name));
|
||||
}
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
if (!target.type().isAssignableFrom(factory.fallback)) {
|
||||
throw new IllegalStateException(
|
||||
String.format(
|
||||
"Incompatible fallback instance. Fallback of type %s is not assignable to %s for feign client %s",
|
||||
factory.fallback, target.type(), factory.name));
|
||||
}
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
|
||||
return builder.target(target, (T) fallbackInstance);
|
||||
}
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public void setPath(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public boolean isDecode404() {
|
||||
return decode404;
|
||||
}
|
||||
|
||||
public void setDecode404(boolean decode404) {
|
||||
this.decode404 = decode404;
|
||||
}
|
||||
|
||||
public ApplicationContext getApplicationContext() {
|
||||
return applicationContext;
|
||||
}
|
||||
|
||||
public Class<?> getFallback() {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
public void setFallback(Class<?> fallback) {
|
||||
this.fallback = fallback;
|
||||
}
|
||||
|
||||
public Class<?> getFallbackFactory() {
|
||||
return fallbackFactory;
|
||||
}
|
||||
|
||||
public void setFallbackFactory(Class<?> fallbackFactory) {
|
||||
this.fallbackFactory = fallbackFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
FeignClientFactoryBean that = (FeignClientFactoryBean) o;
|
||||
return Objects.equals(applicationContext, that.applicationContext) &&
|
||||
decode404 == that.decode404 &&
|
||||
Objects.equals(fallback, that.fallback) &&
|
||||
Objects.equals(fallbackFactory, that.fallbackFactory) &&
|
||||
Objects.equals(name, that.name) &&
|
||||
Objects.equals(path, that.path) &&
|
||||
Objects.equals(type, that.type) &&
|
||||
Objects.equals(url, that.url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(applicationContext, decode404, fallback, fallbackFactory,
|
||||
name, path, type, url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new StringBuilder("FeignClientFactoryBean{")
|
||||
.append("type=").append(type).append(", ")
|
||||
.append("name='").append(name).append("', ")
|
||||
.append("url='").append(url).append("', ")
|
||||
.append("path='").append(path).append("', ")
|
||||
.append("decode404=").append(decode404).append(", ")
|
||||
.append("applicationContext=").append(applicationContext).append(", ")
|
||||
.append("fallback=").append(fallback).append(", ")
|
||||
.append("fallbackFactory=").append(fallbackFactory)
|
||||
.append("}").toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cloud.netflix.feign;
|
||||
|
||||
import feign.Logger;
|
||||
import feign.RequestInterceptor;
|
||||
import feign.Retryer;
|
||||
import feign.codec.ErrorDecoder;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author Eko Kurniawan Khannedy
|
||||
*/
|
||||
@ConfigurationProperties("feign.client")
|
||||
public class FeignClientProperties {
|
||||
|
||||
private boolean defaultToProperties = true;
|
||||
|
||||
private String defaultConfig = "default";
|
||||
|
||||
private Map<String, FeignClientConfiguration> config = new HashMap<>();
|
||||
|
||||
public boolean isDefaultToProperties() {
|
||||
return defaultToProperties;
|
||||
}
|
||||
|
||||
public void setDefaultToProperties(boolean defaultToProperties) {
|
||||
this.defaultToProperties = defaultToProperties;
|
||||
}
|
||||
|
||||
public String getDefaultConfig() {
|
||||
return defaultConfig;
|
||||
}
|
||||
|
||||
public void setDefaultConfig(String defaultConfig) {
|
||||
this.defaultConfig = defaultConfig;
|
||||
}
|
||||
|
||||
public Map<String, FeignClientConfiguration> getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
public void setConfig(Map<String, FeignClientConfiguration> config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
FeignClientProperties that = (FeignClientProperties) o;
|
||||
return defaultToProperties == that.defaultToProperties &&
|
||||
Objects.equals(defaultConfig, that.defaultConfig) &&
|
||||
Objects.equals(config, that.config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(defaultToProperties, defaultConfig, config);
|
||||
}
|
||||
|
||||
public static class FeignClientConfiguration {
|
||||
|
||||
private Logger.Level loggerLevel;
|
||||
|
||||
private Integer connectTimeout;
|
||||
|
||||
private Integer readTimeout;
|
||||
|
||||
private Class<Retryer> retryer;
|
||||
|
||||
private Class<ErrorDecoder> errorDecoder;
|
||||
|
||||
private List<Class<RequestInterceptor>> requestInterceptors;
|
||||
|
||||
private Boolean decode404;
|
||||
|
||||
public Logger.Level getLoggerLevel() {
|
||||
return loggerLevel;
|
||||
}
|
||||
|
||||
public void setLoggerLevel(Logger.Level loggerLevel) {
|
||||
this.loggerLevel = loggerLevel;
|
||||
}
|
||||
|
||||
public Integer getConnectTimeout() {
|
||||
return connectTimeout;
|
||||
}
|
||||
|
||||
public void setConnectTimeout(Integer connectTimeout) {
|
||||
this.connectTimeout = connectTimeout;
|
||||
}
|
||||
|
||||
public Integer getReadTimeout() {
|
||||
return readTimeout;
|
||||
}
|
||||
|
||||
public void setReadTimeout(Integer readTimeout) {
|
||||
this.readTimeout = readTimeout;
|
||||
}
|
||||
|
||||
public Class<Retryer> getRetryer() {
|
||||
return retryer;
|
||||
}
|
||||
|
||||
public void setRetryer(Class<Retryer> retryer) {
|
||||
this.retryer = retryer;
|
||||
}
|
||||
|
||||
public Class<ErrorDecoder> getErrorDecoder() {
|
||||
return errorDecoder;
|
||||
}
|
||||
|
||||
public void setErrorDecoder(Class<ErrorDecoder> errorDecoder) {
|
||||
this.errorDecoder = errorDecoder;
|
||||
}
|
||||
|
||||
public List<Class<RequestInterceptor>> getRequestInterceptors() {
|
||||
return requestInterceptors;
|
||||
}
|
||||
|
||||
public void setRequestInterceptors(List<Class<RequestInterceptor>> requestInterceptors) {
|
||||
this.requestInterceptors = requestInterceptors;
|
||||
}
|
||||
|
||||
public Boolean getDecode404() {
|
||||
return decode404;
|
||||
}
|
||||
|
||||
public void setDecode404(Boolean decode404) {
|
||||
this.decode404 = decode404;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
FeignClientConfiguration that = (FeignClientConfiguration) o;
|
||||
return loggerLevel == that.loggerLevel &&
|
||||
Objects.equals(connectTimeout, that.connectTimeout) &&
|
||||
Objects.equals(readTimeout, that.readTimeout) &&
|
||||
Objects.equals(retryer, that.retryer) &&
|
||||
Objects.equals(errorDecoder, that.errorDecoder) &&
|
||||
Objects.equals(requestInterceptors, that.requestInterceptors) &&
|
||||
Objects.equals(decode404, that.decode404);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(loggerLevel, connectTimeout, readTimeout, retryer,
|
||||
errorDecoder, requestInterceptors, decode404);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+49
-7
@@ -18,20 +18,62 @@ package org.springframework.cloud.netflix.feign;
|
||||
|
||||
import org.springframework.cloud.context.named.NamedContextFactory;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
* @author Gregor Zurowski
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class FeignClientSpecification implements NamedContextFactory.Specification {
|
||||
class FeignClientSpecification implements NamedContextFactory.Specification {
|
||||
|
||||
private String name;
|
||||
|
||||
private Class<?>[] configuration;
|
||||
|
||||
public FeignClientSpecification() {}
|
||||
|
||||
public FeignClientSpecification(String name, Class<?>[] configuration) {
|
||||
this.name = name;
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Class<?>[] getConfiguration() {
|
||||
return configuration;
|
||||
}
|
||||
|
||||
public void setConfiguration(Class<?>[] configuration) {
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
FeignClientSpecification that = (FeignClientSpecification) o;
|
||||
return Objects.equals(name, that.name) &&
|
||||
Arrays.equals(configuration, that.configuration);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name, configuration);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new StringBuilder("FeignClientSpecification{")
|
||||
.append("name='").append(name).append("', ")
|
||||
.append("configuration=").append(Arrays.toString(configuration))
|
||||
.append("}").toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+20
-21
@@ -19,7 +19,6 @@ package org.springframework.cloud.netflix.feign;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.springframework.beans.factory.ObjectFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
@@ -39,16 +38,17 @@ import org.springframework.format.support.FormattingConversionService;
|
||||
|
||||
import com.netflix.hystrix.HystrixCommand;
|
||||
|
||||
import feign.Client;
|
||||
import feign.Contract;
|
||||
import feign.Feign;
|
||||
import feign.Logger;
|
||||
import feign.Retryer;
|
||||
import feign.codec.Decoder;
|
||||
import feign.codec.Encoder;
|
||||
import feign.httpclient.ApacheHttpClient;
|
||||
import feign.hystrix.HystrixFeign;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
* @author Venil Noronha
|
||||
*/
|
||||
@Configuration
|
||||
public class FeignClientsConfiguration {
|
||||
@@ -62,6 +62,9 @@ public class FeignClientsConfiguration {
|
||||
@Autowired(required = false)
|
||||
private List<FeignFormatterRegistrar> feignFormatterRegistrars = new ArrayList<>();
|
||||
|
||||
@Autowired(required = false)
|
||||
private Logger logger;
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public Decoder feignDecoder() {
|
||||
@@ -95,33 +98,29 @@ public class FeignClientsConfiguration {
|
||||
@Bean
|
||||
@Scope("prototype")
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(name = "feign.hystrix.enabled", matchIfMissing = true)
|
||||
@ConditionalOnProperty(name = "feign.hystrix.enabled", matchIfMissing = false)
|
||||
public Feign.Builder feignHystrixBuilder() {
|
||||
return HystrixFeign.builder();
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public Retryer feignRetryer() {
|
||||
return Retryer.NEVER_RETRY;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Scope("prototype")
|
||||
@ConditionalOnMissingBean
|
||||
public Feign.Builder feignBuilder() {
|
||||
return Feign.builder();
|
||||
public Feign.Builder feignBuilder(Retryer retryer) {
|
||||
return Feign.builder().retryer(retryer);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnClass(ApacheHttpClient.class)
|
||||
protected static class HttpClientConfiguration {
|
||||
|
||||
@Autowired(required = false)
|
||||
private HttpClient httpClient;
|
||||
|
||||
@ConditionalOnMissingBean
|
||||
@Bean
|
||||
public Client feignClient() {
|
||||
if (this.httpClient != null) {
|
||||
return new ApacheHttpClient(this.httpClient);
|
||||
}
|
||||
return new ApacheHttpClient();
|
||||
}
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(FeignLoggerFactory.class)
|
||||
public FeignLoggerFactory feignLoggerFactory() {
|
||||
return new DefaultFeignLoggerFactory(logger);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+50
-21
@@ -37,9 +37,12 @@ import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.EnvironmentAware;
|
||||
import org.springframework.context.ResourceLoaderAware;
|
||||
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
|
||||
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.core.type.ClassMetadata;
|
||||
@@ -57,8 +60,8 @@ import org.springframework.util.StringUtils;
|
||||
* @author Jakub Narloch
|
||||
* @author Venil Noronha
|
||||
*/
|
||||
public class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
|
||||
ResourceLoaderAware, BeanClassLoaderAware {
|
||||
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
|
||||
ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {
|
||||
|
||||
// patterned after Spring Integration IntegrationComponentScanRegistrar
|
||||
// and RibbonClientsConfigurationRegistgrar
|
||||
@@ -67,6 +70,8 @@ public class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
|
||||
|
||||
private ClassLoader classLoader;
|
||||
|
||||
private Environment environment;
|
||||
|
||||
public FeignClientsRegistrar() {
|
||||
}
|
||||
|
||||
@@ -178,30 +183,33 @@ public class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
|
||||
definition.addPropertyValue("type", className);
|
||||
definition.addPropertyValue("decode404", attributes.get("decode404"));
|
||||
definition.addPropertyValue("fallback", attributes.get("fallback"));
|
||||
definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
|
||||
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
|
||||
|
||||
String alias = name + "FeignClient";
|
||||
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
|
||||
beanDefinition.setPrimary(true);
|
||||
|
||||
boolean primary = (Boolean)attributes.get("primary"); // has a default, won't be null
|
||||
|
||||
beanDefinition.setPrimary(primary);
|
||||
|
||||
String qualifier = getQualifier(attributes);
|
||||
if (StringUtils.hasText(qualifier)) {
|
||||
alias = qualifier;
|
||||
}
|
||||
|
||||
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
|
||||
new String[] { alias });
|
||||
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
|
||||
}
|
||||
|
||||
private void validate(Map<String, Object> attributes) {
|
||||
if (StringUtils.hasText((String) attributes.get("value"))) {
|
||||
Assert.isTrue(!StringUtils.hasText((String) attributes.get("name")),
|
||||
"Either name or value can be specified, but not both");
|
||||
Assert.isTrue(!StringUtils.hasText((String) attributes.get("serviceId")),
|
||||
"Either name (serviceId) or value can be specified, but not both");
|
||||
}
|
||||
if (StringUtils.hasText((String) attributes.get("name"))) {
|
||||
Assert.isTrue(!StringUtils.hasText((String) attributes.get("serviceId")),
|
||||
"Either name or serviceId can be specified, but not both");
|
||||
}
|
||||
AnnotationAttributes annotation = AnnotationAttributes.fromMap(attributes);
|
||||
// This blows up if an aliased property is overspecified
|
||||
annotation.getAliasedString("name", FeignClient.class, null);
|
||||
}
|
||||
|
||||
private String getName(Map<String, Object> attributes) {
|
||||
/* for testing */ String getName(Map<String, Object> attributes) {
|
||||
String name = (String) attributes.get("serviceId");
|
||||
if (!StringUtils.hasText(name)) {
|
||||
name = (String) attributes.get("name");
|
||||
@@ -216,7 +224,14 @@ public class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
|
||||
|
||||
String host = null;
|
||||
try {
|
||||
host = new URI("http://" + name).getHost();
|
||||
String url;
|
||||
if (!name.startsWith("http://") && !name.startsWith("https://")) {
|
||||
url = "http://" + name;
|
||||
} else {
|
||||
url = name;
|
||||
}
|
||||
host = new URI(url).getHost();
|
||||
|
||||
}
|
||||
catch (URISyntaxException e) {
|
||||
}
|
||||
@@ -225,17 +240,15 @@ public class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
|
||||
}
|
||||
|
||||
private String resolve(String value) {
|
||||
if (StringUtils.hasText(value)
|
||||
&& this.resourceLoader instanceof ConfigurableApplicationContext) {
|
||||
return ((ConfigurableApplicationContext) this.resourceLoader).getEnvironment()
|
||||
.resolvePlaceholders(value);
|
||||
if (StringUtils.hasText(value)) {
|
||||
return this.environment.resolvePlaceholders(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private String getUrl(Map<String, Object> attributes) {
|
||||
String url = resolve((String) attributes.get("url"));
|
||||
if (StringUtils.hasText(url)) {
|
||||
if (StringUtils.hasText(url) && !(url.startsWith("#{") && url.contains("}"))) {
|
||||
if (!url.contains("://")) {
|
||||
url = "http://" + url;
|
||||
}
|
||||
@@ -264,7 +277,7 @@ public class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
|
||||
}
|
||||
|
||||
protected ClassPathScanningCandidateComponentProvider getScanner() {
|
||||
return new ClassPathScanningCandidateComponentProvider(false) {
|
||||
return new ClassPathScanningCandidateComponentProvider(false, this.environment) {
|
||||
|
||||
@Override
|
||||
protected boolean isCandidateComponent(
|
||||
@@ -323,6 +336,17 @@ public class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
|
||||
}
|
||||
return basePackages;
|
||||
}
|
||||
|
||||
private String getQualifier(Map<String, Object> client) {
|
||||
if (client == null) {
|
||||
return null;
|
||||
}
|
||||
String qualifier = (String) client.get("qualifier");
|
||||
if (StringUtils.hasText(qualifier)) {
|
||||
return qualifier;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getClientName(Map<String, Object> client) {
|
||||
if (client == null) {
|
||||
@@ -354,6 +378,11 @@ public class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
|
||||
builder.getBeanDefinition());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnvironment(Environment environment) {
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class to create a {@link TypeFilter} that matches if all the delegates
|
||||
* match.
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.netflix.feign;
|
||||
|
||||
import feign.Logger;
|
||||
|
||||
/**
|
||||
* Allows an application to use a custom Feign {@link Logger}.
|
||||
*
|
||||
* @author Venil Noronha
|
||||
*/
|
||||
public interface FeignLoggerFactory {
|
||||
|
||||
/**
|
||||
* Factory method to provide a {@link Logger} for a given {@link Class}.
|
||||
*
|
||||
* @param type the {@link Class} for which a {@link Logger} instance is to be created
|
||||
* @return a {@link Logger} instance
|
||||
*/
|
||||
public Logger create(Class<?> type);
|
||||
|
||||
}
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright 2013-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.netflix.feign;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import feign.Feign;
|
||||
import feign.Target;
|
||||
import feign.hystrix.FallbackFactory;
|
||||
import feign.hystrix.HystrixFeign;
|
||||
import feign.hystrix.SetterFactory;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
* @author Erik Kringen
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
class HystrixTargeter implements Targeter {
|
||||
|
||||
@Override
|
||||
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
|
||||
Target.HardCodedTarget<T> target) {
|
||||
if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
|
||||
return feign.target(target);
|
||||
}
|
||||
feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
|
||||
SetterFactory setterFactory = getOptional(factory.getName(), context,
|
||||
SetterFactory.class);
|
||||
if (setterFactory != null) {
|
||||
builder.setterFactory(setterFactory);
|
||||
}
|
||||
Class<?> fallback = factory.getFallback();
|
||||
if (fallback != void.class) {
|
||||
return targetWithFallback(factory.getName(), context, target, builder, fallback);
|
||||
}
|
||||
Class<?> fallbackFactory = factory.getFallbackFactory();
|
||||
if (fallbackFactory != void.class) {
|
||||
return targetWithFallbackFactory(factory.getName(), context, target, builder, fallbackFactory);
|
||||
}
|
||||
|
||||
return feign.target(target);
|
||||
}
|
||||
|
||||
private <T> T targetWithFallbackFactory(String feignClientName, FeignContext context,
|
||||
Target.HardCodedTarget<T> target,
|
||||
HystrixFeign.Builder builder,
|
||||
Class<?> fallbackFactoryClass) {
|
||||
FallbackFactory<? extends T> fallbackFactory = (FallbackFactory<? extends T>)
|
||||
getFromContext("fallbackFactory", feignClientName, context, fallbackFactoryClass, FallbackFactory.class);
|
||||
/* We take a sample fallback from the fallback factory to check if it returns a fallback
|
||||
that is compatible with the annotated feign interface. */
|
||||
Object exampleFallback = fallbackFactory.create(new RuntimeException());
|
||||
Assert.notNull(exampleFallback,
|
||||
String.format(
|
||||
"Incompatible fallbackFactory instance for feign client %s. Factory may not produce null!",
|
||||
feignClientName));
|
||||
if (!target.type().isAssignableFrom(exampleFallback.getClass())) {
|
||||
throw new IllegalStateException(
|
||||
String.format(
|
||||
"Incompatible fallbackFactory instance for feign client %s. Factory produces instances of '%s', but should produce instances of '%s'",
|
||||
feignClientName, exampleFallback.getClass(), target.type()));
|
||||
}
|
||||
return builder.target(target, fallbackFactory);
|
||||
}
|
||||
|
||||
|
||||
private <T> T targetWithFallback(String feignClientName, FeignContext context,
|
||||
Target.HardCodedTarget<T> target,
|
||||
HystrixFeign.Builder builder, Class<?> fallback) {
|
||||
T fallbackInstance = getFromContext("fallback", feignClientName, context, fallback, target.type());
|
||||
return builder.target(target, fallbackInstance);
|
||||
}
|
||||
|
||||
private <T> T getFromContext(String fallbackMechanism, String feignClientName, FeignContext context,
|
||||
Class<?> beanType, Class<T> targetType) {
|
||||
Object fallbackInstance = context.getInstance(feignClientName, beanType);
|
||||
if (fallbackInstance == null) {
|
||||
throw new IllegalStateException(String.format(
|
||||
"No " + fallbackMechanism + " instance of type %s found for feign client %s",
|
||||
beanType, feignClientName));
|
||||
}
|
||||
|
||||
if (!targetType.isAssignableFrom(beanType)) {
|
||||
throw new IllegalStateException(
|
||||
String.format(
|
||||
"Incompatible " + fallbackMechanism + " instance. Fallback/fallbackFactory of type %s is not assignable to %s for feign client %s",
|
||||
beanType, targetType, feignClientName));
|
||||
}
|
||||
return (T) fallbackInstance;
|
||||
}
|
||||
|
||||
private <T> T getOptional(String feignClientName, FeignContext context,
|
||||
Class<T> beanType) {
|
||||
return context.getInstance(feignClientName, beanType);
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2013-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.netflix.feign;
|
||||
|
||||
import feign.Feign;
|
||||
import feign.Target;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
interface Targeter {
|
||||
<T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
|
||||
Target.HardCodedTarget<T> target);
|
||||
}
|
||||
+34
-32
@@ -17,6 +17,7 @@
|
||||
package org.springframework.cloud.netflix.feign.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -32,44 +33,45 @@ import static feign.Util.emptyToNull;
|
||||
* {@link PathVariable} parameter processor.
|
||||
*
|
||||
* @author Jakub Narloch
|
||||
* @author Abhijit Sarkar
|
||||
* @see AnnotatedParameterProcessor
|
||||
*/
|
||||
public class PathVariableParameterProcessor implements AnnotatedParameterProcessor {
|
||||
|
||||
private static final Class<PathVariable> ANNOTATION = PathVariable.class;
|
||||
private static final Class<PathVariable> ANNOTATION = PathVariable.class;
|
||||
|
||||
@Override
|
||||
public Class<? extends Annotation> getAnnotationType() {
|
||||
return ANNOTATION;
|
||||
}
|
||||
@Override
|
||||
public Class<? extends Annotation> getAnnotationType() {
|
||||
return ANNOTATION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean processArgument(AnnotatedParameterContext context, Annotation annotation) {
|
||||
String name = ANNOTATION.cast(annotation).value();
|
||||
checkState(emptyToNull(name) != null,
|
||||
"PathVariable annotation was empty on param %s.", context.getParameterIndex());
|
||||
context.setParameterName(name);
|
||||
@Override
|
||||
public boolean processArgument(AnnotatedParameterContext context, Annotation annotation, Method method) {
|
||||
String name = ANNOTATION.cast(annotation).value();
|
||||
checkState(emptyToNull(name) != null,
|
||||
"PathVariable annotation was empty on param %s.", context.getParameterIndex());
|
||||
context.setParameterName(name);
|
||||
|
||||
MethodMetadata data = context.getMethodMetadata();
|
||||
String varName = '{' + name + '}';
|
||||
if (!data.template().url().contains(varName)
|
||||
&& !searchMapValues(data.template().queries(), varName)
|
||||
&& !searchMapValues(data.template().headers(), varName)) {
|
||||
data.formParams().add(name);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
MethodMetadata data = context.getMethodMetadata();
|
||||
String varName = '{' + name + '}';
|
||||
if (!data.template().url().contains(varName)
|
||||
&& !searchMapValues(data.template().queries(), varName)
|
||||
&& !searchMapValues(data.template().headers(), varName)) {
|
||||
data.formParams().add(name);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private <K, V> boolean searchMapValues(Map<K, Collection<V>> map, V search) {
|
||||
Collection<Collection<V>> values = map.values();
|
||||
if (values == null) {
|
||||
return false;
|
||||
}
|
||||
for (Collection<V> entry : values) {
|
||||
if (entry.contains(search)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
private <K, V> boolean searchMapValues(Map<K, Collection<V>> map, V search) {
|
||||
Collection<Collection<V>> values = map.values();
|
||||
if (values == null) {
|
||||
return false;
|
||||
}
|
||||
for (Collection<V> entry : values) {
|
||||
if (entry.contains(search)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
+29
-16
@@ -17,7 +17,9 @@
|
||||
package org.springframework.cloud.netflix.feign.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.cloud.netflix.feign.AnnotatedParameterProcessor;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
@@ -31,27 +33,38 @@ import static feign.Util.emptyToNull;
|
||||
* {@link RequestHeader} parameter processor.
|
||||
*
|
||||
* @author Jakub Narloch
|
||||
* @author Abhijit Sarkar
|
||||
* @see AnnotatedParameterProcessor
|
||||
*/
|
||||
public class RequestHeaderParameterProcessor implements AnnotatedParameterProcessor {
|
||||
|
||||
private static final Class<RequestHeader> ANNOTATION = RequestHeader.class;
|
||||
private static final Class<RequestHeader> ANNOTATION = RequestHeader.class;
|
||||
|
||||
@Override
|
||||
public Class<? extends Annotation> getAnnotationType() {
|
||||
return ANNOTATION;
|
||||
}
|
||||
@Override
|
||||
public Class<? extends Annotation> getAnnotationType() {
|
||||
return ANNOTATION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean processArgument(AnnotatedParameterContext context, Annotation annotation) {
|
||||
String name = ANNOTATION.cast(annotation).value();
|
||||
checkState(emptyToNull(name) != null,
|
||||
"RequestHeader.value() was empty on parameter %s", context.getParameterIndex());
|
||||
context.setParameterName(name);
|
||||
@Override
|
||||
public boolean processArgument(AnnotatedParameterContext context, Annotation annotation, Method method) {
|
||||
int parameterIndex = context.getParameterIndex();
|
||||
Class<?> parameterType = method.getParameterTypes()[parameterIndex];
|
||||
MethodMetadata data = context.getMethodMetadata();
|
||||
|
||||
MethodMetadata data = context.getMethodMetadata();
|
||||
Collection<String> header = context.setTemplateParameter(name, data.template().headers().get(name));
|
||||
data.template().header(name, header);
|
||||
return true;
|
||||
}
|
||||
if (Map.class.isAssignableFrom(parameterType)) {
|
||||
checkState(data.headerMapIndex() == null, "Header map can only be present once.");
|
||||
data.headerMapIndex(parameterIndex);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
String name = ANNOTATION.cast(annotation).value();
|
||||
checkState(emptyToNull(name) != null,
|
||||
"RequestHeader.value() was empty on parameter %s", parameterIndex);
|
||||
context.setParameterName(name);
|
||||
|
||||
Collection<String> header = context.setTemplateParameter(name, data.template().headers().get(name));
|
||||
data.template().header(name, header);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
+16
-4
@@ -17,7 +17,9 @@
|
||||
package org.springframework.cloud.netflix.feign.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.cloud.netflix.feign.AnnotatedParameterProcessor;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
@@ -31,6 +33,7 @@ import feign.MethodMetadata;
|
||||
* {@link RequestParam} parameter processor.
|
||||
*
|
||||
* @author Jakub Narloch
|
||||
* @author Abhijit Sarkar
|
||||
* @see AnnotatedParameterProcessor
|
||||
*/
|
||||
public class RequestParamParameterProcessor implements AnnotatedParameterProcessor {
|
||||
@@ -43,16 +46,25 @@ public class RequestParamParameterProcessor implements AnnotatedParameterProcess
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean processArgument(AnnotatedParameterContext context,
|
||||
Annotation annotation) {
|
||||
public boolean processArgument(AnnotatedParameterContext context, Annotation annotation, Method method) {
|
||||
int parameterIndex = context.getParameterIndex();
|
||||
Class<?> parameterType = method.getParameterTypes()[parameterIndex];
|
||||
MethodMetadata data = context.getMethodMetadata();
|
||||
|
||||
if (Map.class.isAssignableFrom(parameterType)) {
|
||||
checkState(data.queryMapIndex() == null, "Query map can only be present once.");
|
||||
data.queryMapIndex(parameterIndex);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
RequestParam requestParam = ANNOTATION.cast(annotation);
|
||||
String name = requestParam.value();
|
||||
checkState(emptyToNull(name) != null,
|
||||
"RequestParam.value() was empty on parameter %s",
|
||||
context.getParameterIndex());
|
||||
parameterIndex);
|
||||
context.setParameterName(name);
|
||||
|
||||
MethodMetadata data = context.getMethodMetadata();
|
||||
Collection<String> query = context.setTemplateParameter(name,
|
||||
data.template().queries().get(name));
|
||||
data.template().query(name, query);
|
||||
|
||||
+31
-29
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,8 +18,6 @@ package org.springframework.cloud.netflix.feign.encoding;
|
||||
|
||||
import feign.RequestInterceptor;
|
||||
import feign.RequestTemplate;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -29,33 +27,37 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public abstract class BaseRequestInterceptor implements RequestInterceptor {
|
||||
|
||||
/**
|
||||
* The encoding properties.
|
||||
*/
|
||||
@Getter(AccessLevel.PROTECTED)
|
||||
private final FeignClientEncodingProperties properties;
|
||||
/**
|
||||
* The encoding properties.
|
||||
*/
|
||||
private final FeignClientEncodingProperties properties;
|
||||
|
||||
/**
|
||||
* Creates new instance of {@link BaseRequestInterceptor}.
|
||||
*
|
||||
* @param properties the encoding properties
|
||||
*/
|
||||
protected BaseRequestInterceptor(FeignClientEncodingProperties properties) {
|
||||
Assert.notNull(properties, "Properties can not be null");
|
||||
this.properties = properties;
|
||||
}
|
||||
/**
|
||||
* Creates new instance of {@link BaseRequestInterceptor}.
|
||||
*
|
||||
* @param properties the encoding properties
|
||||
*/
|
||||
protected BaseRequestInterceptor(FeignClientEncodingProperties properties) {
|
||||
Assert.notNull(properties, "Properties can not be null");
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the header if it wasn't yet specified.
|
||||
*
|
||||
* @param requestTemplate the request
|
||||
* @param name the header name
|
||||
* @param values the header values
|
||||
*/
|
||||
protected void addHeader(RequestTemplate requestTemplate, String name, String... values) {
|
||||
/**
|
||||
* Adds the header if it wasn't yet specified.
|
||||
*
|
||||
* @param requestTemplate the request
|
||||
* @param name the header name
|
||||
* @param values the header values
|
||||
*/
|
||||
protected void addHeader(RequestTemplate requestTemplate, String name, String... values) {
|
||||
|
||||
if (!requestTemplate.headers().containsKey(name)) {
|
||||
requestTemplate.header(name, values);
|
||||
}
|
||||
}
|
||||
|
||||
protected FeignClientEncodingProperties getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
if (!requestTemplate.headers().containsKey(name)) {
|
||||
requestTemplate.header(name, values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+8
-7
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,7 +18,8 @@ package org.springframework.cloud.netflix.feign.encoding;
|
||||
|
||||
import feign.Feign;
|
||||
import feign.httpclient.ApacheHttpClient;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
@@ -38,11 +39,11 @@ import org.springframework.context.annotation.Configuration;
|
||||
@ConditionalOnClass(Feign.class)
|
||||
@ConditionalOnBean(ApacheHttpClient.class)
|
||||
@ConditionalOnProperty(value = "feign.compression.response.enabled", matchIfMissing = false)
|
||||
@AutoConfigureBefore(FeignAutoConfiguration.class)
|
||||
@AutoConfigureAfter(FeignAutoConfiguration.class)
|
||||
public class FeignAcceptGzipEncodingAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public FeignAcceptGzipEncodingInterceptor feignAcceptGzipEncodingInterceptor(FeignClientEncodingProperties properties) {
|
||||
return new FeignAcceptGzipEncodingInterceptor(properties);
|
||||
}
|
||||
@Bean
|
||||
public FeignAcceptGzipEncodingInterceptor feignAcceptGzipEncodingInterceptor(FeignClientEncodingProperties properties) {
|
||||
return new FeignAcceptGzipEncodingInterceptor(properties);
|
||||
}
|
||||
}
|
||||
|
||||
+17
-17
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -28,22 +28,22 @@ import feign.RequestTemplate;
|
||||
*/
|
||||
public class FeignAcceptGzipEncodingInterceptor extends BaseRequestInterceptor {
|
||||
|
||||
/**
|
||||
* Creates new instance of {@link FeignAcceptGzipEncodingInterceptor}.
|
||||
*
|
||||
* @param properties the encoding properties
|
||||
*/
|
||||
protected FeignAcceptGzipEncodingInterceptor(FeignClientEncodingProperties properties) {
|
||||
super(properties);
|
||||
}
|
||||
/**
|
||||
* Creates new instance of {@link FeignAcceptGzipEncodingInterceptor}.
|
||||
*
|
||||
* @param properties the encoding properties
|
||||
*/
|
||||
protected FeignAcceptGzipEncodingInterceptor(FeignClientEncodingProperties properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void apply(RequestTemplate template) {
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void apply(RequestTemplate template) {
|
||||
|
||||
addHeader(template, HttpEncoding.ACCEPT_ENCODING_HEADER, HttpEncoding.GZIP_ENCODING,
|
||||
HttpEncoding.DEFLATE_ENCODING);
|
||||
}
|
||||
addHeader(template, HttpEncoding.ACCEPT_ENCODING_HEADER, HttpEncoding.GZIP_ENCODING,
|
||||
HttpEncoding.DEFLATE_ENCODING);
|
||||
}
|
||||
}
|
||||
|
||||
+51
-11
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,25 +16,65 @@
|
||||
|
||||
package org.springframework.cloud.netflix.feign.encoding;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* The Feign encoding properties.
|
||||
*
|
||||
* @author Jakub Narloch
|
||||
*/
|
||||
@Data
|
||||
@ConfigurationProperties("feign.compression.request")
|
||||
public class FeignClientEncodingProperties {
|
||||
|
||||
/**
|
||||
* The list of supported mime types.
|
||||
*/
|
||||
private String[] mimeTypes = new String[]{"text/xml", "application/xml", "application/json"};
|
||||
/**
|
||||
* The list of supported mime types.
|
||||
*/
|
||||
private String[] mimeTypes = new String[]{"text/xml", "application/xml", "application/json"};
|
||||
|
||||
/**
|
||||
* The minimum threshold content size.
|
||||
*/
|
||||
private int minRequestSize = 2048;
|
||||
|
||||
public String[] getMimeTypes() {
|
||||
return mimeTypes;
|
||||
}
|
||||
|
||||
public void setMimeTypes(String[] mimeTypes) {
|
||||
this.mimeTypes = mimeTypes;
|
||||
}
|
||||
|
||||
public int getMinRequestSize() {
|
||||
return minRequestSize;
|
||||
}
|
||||
|
||||
public void setMinRequestSize(int minRequestSize) {
|
||||
this.minRequestSize = minRequestSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
FeignClientEncodingProperties that = (FeignClientEncodingProperties) o;
|
||||
return Arrays.equals(mimeTypes, that.mimeTypes) &&
|
||||
Objects.equals(minRequestSize, that.minRequestSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(mimeTypes, minRequestSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new StringBuilder("FeignClientEncodingProperties{")
|
||||
.append("mimeTypes=").append(Arrays.toString(mimeTypes)).append(", ")
|
||||
.append("minRequestSize=").append(minRequestSize)
|
||||
.append("}").toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* The minimum threshold content size.
|
||||
*/
|
||||
private int minRequestSize = 2048;
|
||||
}
|
||||
|
||||
+8
-7
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,7 +18,8 @@ package org.springframework.cloud.netflix.feign.encoding;
|
||||
|
||||
import feign.Feign;
|
||||
import feign.httpclient.ApacheHttpClient;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
@@ -38,11 +39,11 @@ import org.springframework.context.annotation.Configuration;
|
||||
@ConditionalOnClass(Feign.class)
|
||||
@ConditionalOnBean(ApacheHttpClient.class)
|
||||
@ConditionalOnProperty(value = "feign.compression.request.enabled", matchIfMissing = false)
|
||||
@AutoConfigureBefore(FeignAutoConfiguration.class)
|
||||
@AutoConfigureAfter(FeignAutoConfiguration.class)
|
||||
public class FeignContentGzipEncodingAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public FeignContentGzipEncodingInterceptor feignContentGzipEncodingInterceptor(FeignClientEncodingProperties properties) {
|
||||
return new FeignContentGzipEncodingInterceptor(properties);
|
||||
}
|
||||
@Bean
|
||||
public FeignContentGzipEncodingInterceptor feignContentGzipEncodingInterceptor(FeignClientEncodingProperties properties) {
|
||||
return new FeignContentGzipEncodingInterceptor(properties);
|
||||
}
|
||||
}
|
||||
|
||||
+70
-70
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -28,84 +28,84 @@ import java.util.Map;
|
||||
*/
|
||||
public class FeignContentGzipEncodingInterceptor extends BaseRequestInterceptor {
|
||||
|
||||
/**
|
||||
* Creates new instance of {@link FeignContentGzipEncodingInterceptor}.
|
||||
*
|
||||
* @param properties the encoding properties
|
||||
*/
|
||||
protected FeignContentGzipEncodingInterceptor(FeignClientEncodingProperties properties) {
|
||||
super(properties);
|
||||
}
|
||||
/**
|
||||
* Creates new instance of {@link FeignContentGzipEncodingInterceptor}.
|
||||
*
|
||||
* @param properties the encoding properties
|
||||
*/
|
||||
protected FeignContentGzipEncodingInterceptor(FeignClientEncodingProperties properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void apply(RequestTemplate template) {
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void apply(RequestTemplate template) {
|
||||
|
||||
if (requiresCompression(template)) {
|
||||
addHeader(template, HttpEncoding.CONTENT_ENCODING_HEADER, HttpEncoding.GZIP_ENCODING,
|
||||
HttpEncoding.DEFLATE_ENCODING);
|
||||
}
|
||||
}
|
||||
if (requiresCompression(template)) {
|
||||
addHeader(template, HttpEncoding.CONTENT_ENCODING_HEADER, HttpEncoding.GZIP_ENCODING,
|
||||
HttpEncoding.DEFLATE_ENCODING);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the request requires GZIP compression.
|
||||
*
|
||||
* @param template the request template
|
||||
* @return true if request requires compression, false otherwise
|
||||
*/
|
||||
private boolean requiresCompression(RequestTemplate template) {
|
||||
/**
|
||||
* Returns whether the request requires GZIP compression.
|
||||
*
|
||||
* @param template the request template
|
||||
* @return true if request requires compression, false otherwise
|
||||
*/
|
||||
private boolean requiresCompression(RequestTemplate template) {
|
||||
|
||||
final Map<String, Collection<String>> headers = template.headers();
|
||||
return matchesMimeType(headers.get(HttpEncoding.CONTENT_TYPE))
|
||||
&& contentLengthExceedThreshold(headers.get(HttpEncoding.CONTENT_LENGTH));
|
||||
}
|
||||
final Map<String, Collection<String>> headers = template.headers();
|
||||
return matchesMimeType(headers.get(HttpEncoding.CONTENT_TYPE))
|
||||
&& contentLengthExceedThreshold(headers.get(HttpEncoding.CONTENT_LENGTH));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the request content length exceed configured minimum size.
|
||||
*
|
||||
* @param contentLength the content length header value
|
||||
* @return true if length is grater than minimum size, false otherwise
|
||||
*/
|
||||
private boolean contentLengthExceedThreshold(Collection<String> contentLength) {
|
||||
/**
|
||||
* Returns whether the request content length exceed configured minimum size.
|
||||
*
|
||||
* @param contentLength the content length header value
|
||||
* @return true if length is grater than minimum size, false otherwise
|
||||
*/
|
||||
private boolean contentLengthExceedThreshold(Collection<String> contentLength) {
|
||||
|
||||
try {
|
||||
if (contentLength.size() != 1) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
if (contentLength == null || contentLength.size() != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final String strLen = contentLength.iterator().next();
|
||||
final long length = Long.parseLong(strLen);
|
||||
return length > getProperties().getMinRequestSize();
|
||||
} catch (NumberFormatException ex) {
|
||||
// ignores the exception
|
||||
}
|
||||
return false;
|
||||
}
|
||||
final String strLen = contentLength.iterator().next();
|
||||
final long length = Long.parseLong(strLen);
|
||||
return length > getProperties().getMinRequestSize();
|
||||
} catch (NumberFormatException ex) {
|
||||
// ignores the exception
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the content mime types matches the configures mime types.
|
||||
*
|
||||
* @param contentTypes the content types
|
||||
* @return true if any specified content type matches the request content types
|
||||
*/
|
||||
private boolean matchesMimeType(Collection<String> contentTypes) {
|
||||
if (contentTypes == null || contentTypes.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Returns whether the content mime types matches the configures mime types.
|
||||
*
|
||||
* @param contentTypes the content types
|
||||
* @return true if any specified content type matches the request content types
|
||||
*/
|
||||
private boolean matchesMimeType(Collection<String> contentTypes) {
|
||||
if (contentTypes == null || contentTypes.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (getProperties().getMimeTypes() == null || getProperties().getMimeTypes().length == 0) {
|
||||
// no specific mime types has been set - matching everything
|
||||
return true;
|
||||
}
|
||||
if (getProperties().getMimeTypes() == null || getProperties().getMimeTypes().length == 0) {
|
||||
// no specific mime types has been set - matching everything
|
||||
return true;
|
||||
}
|
||||
|
||||
for (String mimeType : getProperties().getMimeTypes()) {
|
||||
if (contentTypes.contains(mimeType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (String mimeType : getProperties().getMimeTypes()) {
|
||||
if (contentTypes.contains(mimeType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
+25
-25
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -23,33 +23,33 @@ package org.springframework.cloud.netflix.feign.encoding;
|
||||
*/
|
||||
public interface HttpEncoding {
|
||||
|
||||
/**
|
||||
* The HTTP Content-Length header.
|
||||
*/
|
||||
String CONTENT_LENGTH = "Content-Length";
|
||||
/**
|
||||
* The HTTP Content-Length header.
|
||||
*/
|
||||
String CONTENT_LENGTH = "Content-Length";
|
||||
|
||||
/**
|
||||
* The HTTP Content-Type header.
|
||||
*/
|
||||
String CONTENT_TYPE = "Content-Type";
|
||||
/**
|
||||
* The HTTP Content-Type header.
|
||||
*/
|
||||
String CONTENT_TYPE = "Content-Type";
|
||||
|
||||
/**
|
||||
* The HTTP Accept-Encoding header.
|
||||
*/
|
||||
String ACCEPT_ENCODING_HEADER = "Accept-Encoding";
|
||||
/**
|
||||
* The HTTP Accept-Encoding header.
|
||||
*/
|
||||
String ACCEPT_ENCODING_HEADER = "Accept-Encoding";
|
||||
|
||||
/**
|
||||
* The HTTP Content-Encoding header.
|
||||
*/
|
||||
String CONTENT_ENCODING_HEADER = "Content-Encoding";
|
||||
/**
|
||||
* The HTTP Content-Encoding header.
|
||||
*/
|
||||
String CONTENT_ENCODING_HEADER = "Content-Encoding";
|
||||
|
||||
/**
|
||||
* The GZIP encoding.
|
||||
*/
|
||||
String GZIP_ENCODING = "gzip";
|
||||
/**
|
||||
* The GZIP encoding.
|
||||
*/
|
||||
String GZIP_ENCODING = "gzip";
|
||||
|
||||
/**
|
||||
* The Deflate encoding.
|
||||
*/
|
||||
String DEFLATE_ENCODING = "deflate";
|
||||
/**
|
||||
* The Deflate encoding.
|
||||
*/
|
||||
String DEFLATE_ENCODING = "deflate";
|
||||
}
|
||||
|
||||
+59
-2
@@ -18,6 +18,10 @@ package org.springframework.cloud.netflix.feign.ribbon;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryListenerFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
|
||||
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancedRetryPolicyFactory;
|
||||
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
|
||||
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
@@ -30,15 +34,67 @@ import com.netflix.loadbalancer.ILoadBalancer;
|
||||
*
|
||||
* @author Spencer Gibb
|
||||
* @author Dave Syer
|
||||
* @author Ryan Baxter
|
||||
* @author Gang Li
|
||||
*/
|
||||
public class CachingSpringLoadBalancerFactory {
|
||||
|
||||
private final SpringClientFactory factory;
|
||||
private final LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory;
|
||||
private final LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory;
|
||||
private final LoadBalancedRetryListenerFactory loadBalancedRetryListenerFactory;
|
||||
private boolean enableRetry = false;
|
||||
|
||||
private volatile Map<String, FeignLoadBalancer> cache = new ConcurrentReferenceHashMap<>();
|
||||
|
||||
public CachingSpringLoadBalancerFactory(SpringClientFactory factory) {
|
||||
this.factory = factory;
|
||||
this.loadBalancedRetryPolicyFactory = new RibbonLoadBalancedRetryPolicyFactory(factory);
|
||||
this.loadBalancedBackOffPolicyFactory = null;
|
||||
this.loadBalancedRetryListenerFactory = null;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
//TODO remove in 2.0.x
|
||||
public CachingSpringLoadBalancerFactory(SpringClientFactory factory,
|
||||
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) {
|
||||
this.factory = factory;
|
||||
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
|
||||
this.loadBalancedBackOffPolicyFactory = null;
|
||||
this.loadBalancedRetryListenerFactory = null;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
//TODO remove in 2.0.0x
|
||||
public CachingSpringLoadBalancerFactory(SpringClientFactory factory,
|
||||
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory, boolean enableRetry) {
|
||||
this.factory = factory;
|
||||
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
|
||||
this.enableRetry = enableRetry;
|
||||
this.loadBalancedBackOffPolicyFactory = null;
|
||||
this.loadBalancedRetryListenerFactory = null;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
//TODO remove in 2.0.0x
|
||||
public CachingSpringLoadBalancerFactory(SpringClientFactory factory,
|
||||
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory,
|
||||
LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory) {
|
||||
this.factory = factory;
|
||||
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
|
||||
this.loadBalancedBackOffPolicyFactory = loadBalancedBackOffPolicyFactory;
|
||||
this.loadBalancedRetryListenerFactory = null;
|
||||
this.enableRetry = true;
|
||||
}
|
||||
|
||||
public CachingSpringLoadBalancerFactory(SpringClientFactory factory, LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory,
|
||||
LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory,
|
||||
LoadBalancedRetryListenerFactory loadBalancedRetryListenerFactory) {
|
||||
this.factory = factory;
|
||||
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
|
||||
this.loadBalancedBackOffPolicyFactory = loadBalancedBackOffPolicyFactory;
|
||||
this.loadBalancedRetryListenerFactory = loadBalancedRetryListenerFactory;
|
||||
this.enableRetry = true;
|
||||
}
|
||||
|
||||
public FeignLoadBalancer create(String clientName) {
|
||||
@@ -48,9 +104,10 @@ public class CachingSpringLoadBalancerFactory {
|
||||
IClientConfig config = this.factory.getClientConfig(clientName);
|
||||
ILoadBalancer lb = this.factory.getLoadBalancer(clientName);
|
||||
ServerIntrospector serverIntrospector = this.factory.getInstance(clientName, ServerIntrospector.class);
|
||||
FeignLoadBalancer client = new FeignLoadBalancer(lb, config, serverIntrospector);
|
||||
FeignLoadBalancer client = enableRetry ? new RetryableFeignLoadBalancer(lb, config, serverIntrospector,
|
||||
loadBalancedRetryPolicyFactory, loadBalancedBackOffPolicyFactory, loadBalancedRetryListenerFactory) : new FeignLoadBalancer(lb, config, serverIntrospector);
|
||||
this.cache.put(clientName, client);
|
||||
return client;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.netflix.feign.ribbon;
|
||||
|
||||
import feign.Client;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
@Configuration
|
||||
class DefaultFeignLoadBalancedConfiguration {
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
|
||||
SpringClientFactory clientFactory) {
|
||||
return new LoadBalancerFeignClient(new Client.Default(null, null),
|
||||
cachingFactory, clientFactory);
|
||||
}
|
||||
}
|
||||
+47
-25
@@ -16,15 +16,22 @@
|
||||
|
||||
package org.springframework.cloud.netflix.feign.ribbon;
|
||||
|
||||
import feign.Client;
|
||||
import feign.Request;
|
||||
import feign.Response;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpRequest;
|
||||
import com.netflix.client.AbstractLoadBalancerAwareClient;
|
||||
import com.netflix.client.ClientException;
|
||||
import com.netflix.client.ClientRequest;
|
||||
@@ -36,22 +43,18 @@ import com.netflix.client.config.IClientConfig;
|
||||
import com.netflix.loadbalancer.ILoadBalancer;
|
||||
import com.netflix.loadbalancer.Server;
|
||||
|
||||
import feign.Client;
|
||||
import feign.Request;
|
||||
import feign.RequestTemplate;
|
||||
import feign.Response;
|
||||
import feign.Util;
|
||||
import static org.springframework.cloud.netflix.ribbon.RibbonUtils.updateToHttpsIfNeeded;
|
||||
|
||||
public class FeignLoadBalancer extends
|
||||
AbstractLoadBalancerAwareClient<FeignLoadBalancer.RibbonRequest, FeignLoadBalancer.RibbonResponse> {
|
||||
|
||||
private final int connectTimeout;
|
||||
private final int readTimeout;
|
||||
private final IClientConfig clientConfig;
|
||||
private final ServerIntrospector serverIntrospector;
|
||||
protected int connectTimeout;
|
||||
protected int readTimeout;
|
||||
protected IClientConfig clientConfig;
|
||||
protected ServerIntrospector serverIntrospector;
|
||||
|
||||
public FeignLoadBalancer(ILoadBalancer lb, IClientConfig clientConfig,
|
||||
ServerIntrospector serverIntrospector) {
|
||||
ServerIntrospector serverIntrospector) {
|
||||
super(lb, clientConfig);
|
||||
this.setRetryHandler(RetryHandler.DEFAULT);
|
||||
this.clientConfig = clientConfig;
|
||||
@@ -98,13 +101,8 @@ public class FeignLoadBalancer extends
|
||||
|
||||
@Override
|
||||
public URI reconstructURIWithServer(Server server, URI original) {
|
||||
String scheme = original.getScheme();
|
||||
if (!"https".equals(scheme) && (this.serverIntrospector.isSecure(server)
|
||||
|| this.clientConfig.get(CommonClientConfigKey.IsSecure, false))) {
|
||||
original = UriComponentsBuilder.fromUri(original).scheme("https").build()
|
||||
.toUri();
|
||||
}
|
||||
return super.reconstructURIWithServer(server, original);
|
||||
URI uri = updateToHttpsIfNeeded(original, this.clientConfig, this.serverIntrospector, server);
|
||||
return super.reconstructURIWithServer(server, uri);
|
||||
}
|
||||
|
||||
static class RibbonRequest extends ClientRequest implements Cloneable {
|
||||
@@ -121,11 +119,7 @@ public class FeignLoadBalancer extends
|
||||
private Request toRequest(Request request) {
|
||||
Map<String, Collection<String>> headers = new LinkedHashMap<>(
|
||||
request.headers());
|
||||
// Apache client barfs if you set the content length
|
||||
headers.remove(Util.CONTENT_LENGTH);
|
||||
return new RequestTemplate().method(request.method())
|
||||
.append(getUri().toASCIIString())
|
||||
.body(request.body(), request.charset()).headers(headers).request();
|
||||
return Request.create(request.method(),getUri().toASCIIString(),headers,request.body(),request.charset());
|
||||
}
|
||||
|
||||
Request toRequest() {
|
||||
@@ -136,6 +130,34 @@ public class FeignLoadBalancer extends
|
||||
return this.client;
|
||||
}
|
||||
|
||||
HttpRequest toHttpRequest() {
|
||||
return new HttpRequest() {
|
||||
@Override
|
||||
public HttpMethod getMethod() {
|
||||
return HttpMethod.resolve(RibbonRequest.this.toRequest().method());
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getURI() {
|
||||
return RibbonRequest.this.getUri();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders getHeaders() {
|
||||
Map<String, List<String>> headers = new HashMap<String, List<String>>();
|
||||
Map<String, Collection<String>> feignHeaders = RibbonRequest.this.toRequest().headers();
|
||||
for(String key : feignHeaders.keySet()) {
|
||||
headers.put(key, new ArrayList<String>(feignHeaders.get(key)));
|
||||
}
|
||||
HttpHeaders httpHeaders = new HttpHeaders();
|
||||
httpHeaders.putAll(headers);
|
||||
return httpHeaders;
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object clone() {
|
||||
return new RibbonRequest(this.client, this.request, getUri());
|
||||
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright 2013-2016 the original author or authors.
|
||||
* *
|
||||
* * Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* * you may not use this file except in compliance with the License.
|
||||
* * You may obtain a copy of the License at
|
||||
* *
|
||||
* * http://www.apache.org/licenses/LICENSE-2.0
|
||||
* *
|
||||
* * Unless required by applicable law or agreed to in writing, software
|
||||
* * distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* * See the License for the specific language governing permissions and
|
||||
* * limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.netflix.feign.ribbon;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.InterceptorRetryPolicy;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy;
|
||||
import org.springframework.cloud.client.loadbalancer.ServiceInstanceChooser;
|
||||
import org.springframework.http.HttpRequest;
|
||||
import org.springframework.retry.RetryContext;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
public class FeignRetryPolicy extends InterceptorRetryPolicy {
|
||||
private HttpRequest request;
|
||||
private String serviceId;
|
||||
public FeignRetryPolicy(HttpRequest request, LoadBalancedRetryPolicy policy, ServiceInstanceChooser serviceInstanceChooser, String serviceName) {
|
||||
super(request, policy, serviceInstanceChooser, serviceName);
|
||||
this.request = request;
|
||||
this.serviceId = serviceName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRetry(RetryContext context) {
|
||||
/*
|
||||
* In InterceptorRetryPolicy.canRetry we ask the LoadBalancer to choose a server if one is not
|
||||
* set in the retry context and then return true. RetryTemplat calls the canRetry method of
|
||||
* the policy even on its first execution. So the fact that we didnt have a service instance set
|
||||
* in the RetryContext signaled that it was the first execution and we should return true.
|
||||
*
|
||||
* In the Feign scenario, Feign as actually already queried the load balancer for a service instance
|
||||
* and we set that service instance in the context when we call the open method of the policy. So in
|
||||
* the Feign case we just return true if the retry count is 0 indicating we haven't yet made a failed
|
||||
* request.
|
||||
*/
|
||||
if(context.getRetryCount() == 0) {
|
||||
return true;
|
||||
}
|
||||
return super.canRetry(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RetryContext open(RetryContext parent) {
|
||||
/*
|
||||
* With Feign (unlike Ribbon) the request already has the URI for the service instance
|
||||
* we are going to make the request to, so extract that information and set the service
|
||||
* instance in the context. In the Ribbon scenario the URI in the request object still has
|
||||
* the service id so we choose and set the service instance later on.
|
||||
*/
|
||||
LoadBalancedRetryContext context = new LoadBalancedRetryContext(parent, this.request);
|
||||
context.setServiceInstance(new FeignRetryPolicyServiceInstance(serviceId, request));
|
||||
return context;
|
||||
}
|
||||
|
||||
class FeignRetryPolicyServiceInstance implements ServiceInstance {
|
||||
|
||||
private String serviceId;
|
||||
private HttpRequest request;
|
||||
private Map<String, String> metadata;
|
||||
|
||||
FeignRetryPolicyServiceInstance(String serviceId, HttpRequest request) {
|
||||
this.serviceId = serviceId;
|
||||
this.request = request;
|
||||
this.metadata = new HashMap<String, String>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServiceId() {
|
||||
return serviceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHost() {
|
||||
return request.getURI().getHost();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPort() {
|
||||
return request.getURI().getPort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSecure() {
|
||||
return "https".equals(request.getURI().getScheme());
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getUri() {
|
||||
return request.getURI();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
}
|
||||
}
|
||||
+24
-59
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,25 +16,26 @@
|
||||
|
||||
package org.springframework.cloud.netflix.feign.ribbon;
|
||||
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryListenerFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
|
||||
import org.springframework.cloud.netflix.feign.FeignAutoConfiguration;
|
||||
import org.springframework.cloud.netflix.feign.support.FeignHttpClientProperties;
|
||||
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
|
||||
import com.netflix.loadbalancer.ILoadBalancer;
|
||||
|
||||
import feign.Client;
|
||||
import feign.Feign;
|
||||
import feign.Request;
|
||||
import feign.httpclient.ApacheHttpClient;
|
||||
import feign.okhttp.OkHttpClient;
|
||||
|
||||
/**
|
||||
* Autoconfiguration to be activated if Feign is in use and needs to be use Ribbon as a
|
||||
@@ -45,21 +46,31 @@ import feign.okhttp.OkHttpClient;
|
||||
@ConditionalOnClass({ ILoadBalancer.class, Feign.class })
|
||||
@Configuration
|
||||
@AutoConfigureBefore(FeignAutoConfiguration.class)
|
||||
@EnableConfigurationProperties({ FeignHttpClientProperties.class })
|
||||
//Order is important here, last should be the default, first should be optional
|
||||
// see https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653
|
||||
@Import({ HttpClientFeignLoadBalancedConfiguration.class,
|
||||
OkHttpFeignLoadBalancedConfiguration.class,
|
||||
DefaultFeignLoadBalancedConfiguration.class })
|
||||
public class FeignRibbonClientAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
|
||||
public CachingSpringLoadBalancerFactory cachingLBClientFactory(
|
||||
SpringClientFactory factory) {
|
||||
return new CachingSpringLoadBalancerFactory(factory);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
|
||||
SpringClientFactory clientFactory) {
|
||||
return new LoadBalancerFeignClient(new Client.Default(null, null),
|
||||
cachingFactory, clientFactory);
|
||||
@Primary
|
||||
@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
|
||||
public CachingSpringLoadBalancerFactory retryabeCachingLBClientFactory(
|
||||
SpringClientFactory factory,
|
||||
LoadBalancedRetryPolicyFactory retryPolicyFactory,
|
||||
LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory,
|
||||
LoadBalancedRetryListenerFactory loadBalancedRetryListenerFactory) {
|
||||
return new CachingSpringLoadBalancerFactory(factory, retryPolicyFactory, loadBalancedBackOffPolicyFactory, loadBalancedRetryListenerFactory);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -67,50 +78,4 @@ public class FeignRibbonClientAutoConfiguration {
|
||||
public Request.Options feignRequestOptions() {
|
||||
return LoadBalancerFeignClient.DEFAULT_OPTIONS;
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnClass(ApacheHttpClient.class)
|
||||
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
|
||||
protected static class HttpClientConfiguration {
|
||||
|
||||
@Autowired(required = false)
|
||||
private HttpClient httpClient;
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(Client.class)
|
||||
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
|
||||
SpringClientFactory clientFactory) {
|
||||
ApacheHttpClient delegate;
|
||||
if (this.httpClient != null) {
|
||||
delegate = new ApacheHttpClient(this.httpClient);
|
||||
}
|
||||
else {
|
||||
delegate = new ApacheHttpClient();
|
||||
}
|
||||
return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnClass(OkHttpClient.class)
|
||||
@ConditionalOnProperty(value = "feign.okhttp.enabled", matchIfMissing = true)
|
||||
protected static class OkHttpConfiguration {
|
||||
|
||||
@Autowired(required = false)
|
||||
private com.squareup.okhttp.OkHttpClient okHttpClient;
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(Client.class)
|
||||
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
|
||||
SpringClientFactory clientFactory) {
|
||||
OkHttpClient delegate;
|
||||
if (this.okHttpClient != null) {
|
||||
delegate = new OkHttpClient(this.okHttpClient);
|
||||
}
|
||||
else {
|
||||
delegate = new OkHttpClient();
|
||||
}
|
||||
return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.netflix.feign.ribbon;
|
||||
|
||||
import feign.Client;
|
||||
import feign.httpclient.ApacheHttpClient;
|
||||
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import javax.annotation.PreDestroy;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.config.RegistryBuilder;
|
||||
import org.apache.http.conn.HttpClientConnectionManager;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.cloud.commons.httpclient.ApacheHttpClientConnectionManagerFactory;
|
||||
import org.springframework.cloud.commons.httpclient.ApacheHttpClientFactory;
|
||||
import org.springframework.cloud.netflix.feign.support.FeignHttpClientProperties;
|
||||
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnClass(ApacheHttpClient.class)
|
||||
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
|
||||
class HttpClientFeignLoadBalancedConfiguration {
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnMissingBean(CloseableHttpClient.class)
|
||||
protected static class HttpClientFeignConfiguration {
|
||||
private final Timer connectionManagerTimer = new Timer(
|
||||
"FeignApacheHttpClientConfiguration.connectionManagerTimer", true);
|
||||
|
||||
private CloseableHttpClient httpClient;
|
||||
|
||||
@Autowired(required = false)
|
||||
private RegistryBuilder registryBuilder;
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(HttpClientConnectionManager.class)
|
||||
public HttpClientConnectionManager connectionManager(
|
||||
ApacheHttpClientConnectionManagerFactory connectionManagerFactory,
|
||||
FeignHttpClientProperties httpClientProperties) {
|
||||
final HttpClientConnectionManager connectionManager = connectionManagerFactory
|
||||
.newConnectionManager(false, httpClientProperties.getMaxConnections(),
|
||||
httpClientProperties.getMaxConnectionsPerRoute(),
|
||||
httpClientProperties.getTimeToLive(),
|
||||
httpClientProperties.getTimeToLiveUnit(), registryBuilder);
|
||||
this.connectionManagerTimer.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
connectionManager.closeExpiredConnections();
|
||||
}
|
||||
}, 30000, httpClientProperties.getConnectionTimerRepeat());
|
||||
return connectionManager;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CloseableHttpClient httpClient(ApacheHttpClientFactory httpClientFactory,
|
||||
HttpClientConnectionManager httpClientConnectionManager,
|
||||
FeignHttpClientProperties httpClientProperties) {
|
||||
RequestConfig defaultRequestConfig = RequestConfig.custom()
|
||||
.setConnectTimeout(httpClientProperties.getConnectionTimeout())
|
||||
.setRedirectsEnabled(httpClientProperties.isFollowRedirects())
|
||||
.build();
|
||||
this.httpClient = httpClientFactory.createBuilder().
|
||||
setDefaultRequestConfig(defaultRequestConfig).
|
||||
setConnectionManager(httpClientConnectionManager).build();
|
||||
return this.httpClient;
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void destroy() throws Exception {
|
||||
connectionManagerTimer.cancel();
|
||||
if(httpClient != null) {
|
||||
httpClient.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(Client.class)
|
||||
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
|
||||
SpringClientFactory clientFactory, HttpClient httpClient) {
|
||||
ApacheHttpClient delegate = new ApacheHttpClient(httpClient);
|
||||
return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.netflix.feign.ribbon;
|
||||
|
||||
import feign.Client;
|
||||
import feign.okhttp.OkHttpClient;
|
||||
import okhttp3.ConnectionPool;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.annotation.PreDestroy;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.cloud.commons.httpclient.OkHttpClientConnectionPoolFactory;
|
||||
import org.springframework.cloud.commons.httpclient.OkHttpClientFactory;
|
||||
import org.springframework.cloud.netflix.feign.support.FeignHttpClientProperties;
|
||||
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnClass(OkHttpClient.class)
|
||||
@ConditionalOnProperty(value = "feign.okhttp.enabled")
|
||||
class OkHttpFeignLoadBalancedConfiguration {
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnMissingBean(okhttp3.OkHttpClient.class)
|
||||
protected static class OkHttpFeignConfiguration {
|
||||
private okhttp3.OkHttpClient okHttpClient;
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(ConnectionPool.class)
|
||||
public ConnectionPool httpClientConnectionPool(FeignHttpClientProperties httpClientProperties,
|
||||
OkHttpClientConnectionPoolFactory connectionPoolFactory) {
|
||||
Integer maxTotalConnections = httpClientProperties.getMaxConnections();
|
||||
Long timeToLive = httpClientProperties.getTimeToLive();
|
||||
TimeUnit ttlUnit = httpClientProperties.getTimeToLiveUnit();
|
||||
return connectionPoolFactory.create(maxTotalConnections, timeToLive, ttlUnit);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public okhttp3.OkHttpClient client(OkHttpClientFactory httpClientFactory,
|
||||
ConnectionPool connectionPool, FeignHttpClientProperties httpClientProperties) {
|
||||
Boolean followRedirects = httpClientProperties.isFollowRedirects();
|
||||
Integer connectTimeout = httpClientProperties.getConnectionTimeout();
|
||||
this.okHttpClient = httpClientFactory.createBuilder(false).
|
||||
connectTimeout(connectTimeout, TimeUnit.MILLISECONDS).
|
||||
followRedirects(followRedirects).
|
||||
connectionPool(connectionPool).build();
|
||||
return this.okHttpClient;
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void destroy() {
|
||||
if(okHttpClient != null) {
|
||||
okHttpClient.dispatcher().executorService().shutdown();
|
||||
okHttpClient.connectionPool().evictAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(Client.class)
|
||||
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
|
||||
SpringClientFactory clientFactory, okhttp3.OkHttpClient okHttpClient) {
|
||||
OkHttpClient delegate = new OkHttpClient(okHttpClient);
|
||||
return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
|
||||
}
|
||||
}
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright 2013-2016 the original author or authors.
|
||||
* *
|
||||
* * Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* * you may not use this file except in compliance with the License.
|
||||
* * You may obtain a copy of the License at
|
||||
* *
|
||||
* * http://www.apache.org/licenses/LICENSE-2.0
|
||||
* *
|
||||
* * Unless required by applicable law or agreed to in writing, software
|
||||
* * distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* * See the License for the specific language governing permissions and
|
||||
* * limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.netflix.feign.ribbon;
|
||||
|
||||
import feign.Request;
|
||||
import feign.Response;
|
||||
|
||||
import java.io.IOException;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryListenerFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.RetryableStatusCodeException;
|
||||
import org.springframework.cloud.client.loadbalancer.ServiceInstanceChooser;
|
||||
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient;
|
||||
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
|
||||
import org.springframework.retry.RetryCallback;
|
||||
import org.springframework.retry.RetryContext;
|
||||
import org.springframework.retry.RetryListener;
|
||||
import org.springframework.retry.backoff.BackOffPolicy;
|
||||
import org.springframework.retry.backoff.NoBackOffPolicy;
|
||||
import org.springframework.retry.policy.NeverRetryPolicy;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
import com.netflix.client.DefaultLoadBalancerRetryHandler;
|
||||
import com.netflix.client.RequestSpecificRetryHandler;
|
||||
import com.netflix.client.config.CommonClientConfigKey;
|
||||
import com.netflix.client.config.IClientConfig;
|
||||
import com.netflix.loadbalancer.ILoadBalancer;
|
||||
import com.netflix.loadbalancer.Server;
|
||||
|
||||
/**
|
||||
* A {@link FeignLoadBalancer} that leverages Spring Retry to retry failed requests.
|
||||
* @author Ryan Baxter
|
||||
* @author Gang Li
|
||||
*/
|
||||
public class RetryableFeignLoadBalancer extends FeignLoadBalancer implements ServiceInstanceChooser {
|
||||
|
||||
private final LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory;
|
||||
private final LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory;
|
||||
private final LoadBalancedRetryListenerFactory loadBalancedRetryListenerFactory;
|
||||
|
||||
@Deprecated
|
||||
//TODO remove in 2.0.x
|
||||
public RetryableFeignLoadBalancer(ILoadBalancer lb, IClientConfig clientConfig,
|
||||
ServerIntrospector serverIntrospector, LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) {
|
||||
super(lb, clientConfig, serverIntrospector);
|
||||
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
|
||||
this.setRetryHandler(new DefaultLoadBalancerRetryHandler(clientConfig));
|
||||
this.loadBalancedBackOffPolicyFactory = new LoadBalancedBackOffPolicyFactory.NoBackOffPolicyFactory();
|
||||
this.loadBalancedRetryListenerFactory = new LoadBalancedRetryListenerFactory.DefaultRetryListenerFactory();
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
//TODO remove in 2.0.x
|
||||
public RetryableFeignLoadBalancer(ILoadBalancer lb, IClientConfig clientConfig,
|
||||
ServerIntrospector serverIntrospector, LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory,
|
||||
LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory) {
|
||||
super(lb, clientConfig, serverIntrospector);
|
||||
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
|
||||
this.setRetryHandler(new DefaultLoadBalancerRetryHandler(clientConfig));
|
||||
this.loadBalancedBackOffPolicyFactory = loadBalancedBackOffPolicyFactory == null ?
|
||||
new LoadBalancedBackOffPolicyFactory.NoBackOffPolicyFactory() : loadBalancedBackOffPolicyFactory;
|
||||
this.loadBalancedRetryListenerFactory = new LoadBalancedRetryListenerFactory.DefaultRetryListenerFactory();
|
||||
}
|
||||
|
||||
public RetryableFeignLoadBalancer(ILoadBalancer lb, IClientConfig clientConfig, ServerIntrospector serverIntrospector,
|
||||
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory,
|
||||
LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory,
|
||||
LoadBalancedRetryListenerFactory loadBalancedRetryListenerFactory) {
|
||||
super(lb, clientConfig, serverIntrospector);
|
||||
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
|
||||
this.setRetryHandler(new DefaultLoadBalancerRetryHandler(clientConfig));
|
||||
this.loadBalancedBackOffPolicyFactory = loadBalancedBackOffPolicyFactory == null ?
|
||||
new LoadBalancedBackOffPolicyFactory.NoBackOffPolicyFactory() : loadBalancedBackOffPolicyFactory;
|
||||
this.loadBalancedRetryListenerFactory = loadBalancedRetryListenerFactory == null ?
|
||||
new LoadBalancedRetryListenerFactory.DefaultRetryListenerFactory() : loadBalancedRetryListenerFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RibbonResponse execute(final RibbonRequest request, IClientConfig configOverride)
|
||||
throws IOException {
|
||||
final Request.Options options;
|
||||
if (configOverride != null) {
|
||||
options = new Request.Options(
|
||||
configOverride.get(CommonClientConfigKey.ConnectTimeout,
|
||||
this.connectTimeout),
|
||||
(configOverride.get(CommonClientConfigKey.ReadTimeout,
|
||||
this.readTimeout)));
|
||||
}
|
||||
else {
|
||||
options = new Request.Options(this.connectTimeout, this.readTimeout);
|
||||
}
|
||||
final LoadBalancedRetryPolicy retryPolicy = loadBalancedRetryPolicyFactory.create(this.getClientName(), this);
|
||||
RetryTemplate retryTemplate = new RetryTemplate();
|
||||
BackOffPolicy backOffPolicy = loadBalancedBackOffPolicyFactory.createBackOffPolicy(this.getClientName());
|
||||
retryTemplate.setBackOffPolicy(backOffPolicy == null ? new NoBackOffPolicy() : backOffPolicy);
|
||||
RetryListener[] retryListeners = this.loadBalancedRetryListenerFactory.createRetryListeners(this.getClientName());
|
||||
if (retryListeners != null && retryListeners.length != 0) {
|
||||
retryTemplate.setListeners(retryListeners);
|
||||
}
|
||||
retryTemplate.setRetryPolicy(retryPolicy == null ? new NeverRetryPolicy()
|
||||
: new FeignRetryPolicy(request.toHttpRequest(), retryPolicy, this, this.getClientName()));
|
||||
return retryTemplate.execute(new RetryCallback<RibbonResponse, IOException>() {
|
||||
@Override
|
||||
public RibbonResponse doWithRetry(RetryContext retryContext) throws IOException {
|
||||
Request feignRequest = null;
|
||||
//on retries the policy will choose the server and set it in the context
|
||||
//extract the server and update the request being made
|
||||
if(retryContext instanceof LoadBalancedRetryContext) {
|
||||
ServiceInstance service = ((LoadBalancedRetryContext)retryContext).getServiceInstance();
|
||||
if(service != null) {
|
||||
feignRequest = ((RibbonRequest)request.replaceUri(reconstructURIWithServer(new Server(service.getHost(), service.getPort()), request.getUri()))).toRequest();
|
||||
}
|
||||
}
|
||||
if(feignRequest == null) {
|
||||
feignRequest = request.toRequest();
|
||||
}
|
||||
Response response = request.client().execute(feignRequest, options);
|
||||
if(retryPolicy.retryableStatusCode(response.status())) {
|
||||
response.close();
|
||||
throw new RetryableStatusCodeException(RetryableFeignLoadBalancer.this.getClientName(), response.status());
|
||||
}
|
||||
return new RibbonResponse(request.getUri(), response);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestSpecificRetryHandler getRequestSpecificRetryHandler(
|
||||
FeignLoadBalancer.RibbonRequest request, IClientConfig requestConfig) {
|
||||
return new RequestSpecificRetryHandler(false, false, this.getRetryHandler(), requestConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServiceInstance choose(String serviceId) {
|
||||
return new RibbonLoadBalancerClient.RibbonServer(serviceId,
|
||||
this.getLoadBalancer().chooseServer(serviceId));
|
||||
}
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright 2013-2016 the original author or authors.
|
||||
* *
|
||||
* * Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* * you may not use this file except in compliance with the License.
|
||||
* * You may obtain a copy of the License at
|
||||
* *
|
||||
* * http://www.apache.org/licenses/LICENSE-2.0
|
||||
* *
|
||||
* * Unless required by applicable law or agreed to in writing, software
|
||||
* * distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* * See the License for the specific language governing permissions and
|
||||
* * limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.netflix.feign.support;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "feign.httpclient")
|
||||
public class FeignHttpClientProperties {
|
||||
public static final boolean DEFAULT_DISABLE_SSL_VALIDATION = false;
|
||||
public static final int DEFAULT_MAX_CONNECTIONS = 200;
|
||||
public static final int DEFAULT_MAX_CONNECTIONS_PER_ROUTE = 50;
|
||||
public static final long DEFAULT_TIME_TO_LIVE = 900L;
|
||||
public static final TimeUnit DEFAULT_TIME_TO_LIVE_UNIT = TimeUnit.SECONDS;
|
||||
public static final boolean DEFAULT_FOLLOW_REDIRECTS = true;
|
||||
public static final int DEFAULT_CONNECTION_TIMEOUT = 2000;
|
||||
public static final int DEFAULT_CONNECTION_TIMER_REPEAT = 3000;
|
||||
|
||||
private boolean disableSslValidation = DEFAULT_DISABLE_SSL_VALIDATION;
|
||||
private int maxConnections = DEFAULT_MAX_CONNECTIONS;
|
||||
private int maxConnectionsPerRoute = DEFAULT_MAX_CONNECTIONS_PER_ROUTE;
|
||||
private long timeToLive = DEFAULT_TIME_TO_LIVE;
|
||||
private TimeUnit timeToLiveUnit = DEFAULT_TIME_TO_LIVE_UNIT;
|
||||
private boolean followRedirects = DEFAULT_FOLLOW_REDIRECTS;
|
||||
private int connectionTimeout = DEFAULT_CONNECTION_TIMEOUT;
|
||||
private int connectionTimerRepeat = DEFAULT_CONNECTION_TIMER_REPEAT;
|
||||
|
||||
public int getConnectionTimerRepeat() {
|
||||
return connectionTimerRepeat;
|
||||
}
|
||||
|
||||
public void setConnectionTimerRepeat(int connectionTimerRepeat) {
|
||||
this.connectionTimerRepeat = connectionTimerRepeat;
|
||||
}
|
||||
|
||||
public boolean isDisableSslValidation() {
|
||||
return disableSslValidation;
|
||||
}
|
||||
|
||||
public void setDisableSslValidation(boolean disableSslValidation) {
|
||||
this.disableSslValidation = disableSslValidation;
|
||||
}
|
||||
|
||||
public int getMaxConnections() {
|
||||
return maxConnections;
|
||||
}
|
||||
|
||||
public void setMaxConnections(int maxConnections) {
|
||||
this.maxConnections = maxConnections;
|
||||
}
|
||||
|
||||
public int getMaxConnectionsPerRoute() {
|
||||
return maxConnectionsPerRoute;
|
||||
}
|
||||
|
||||
public void setMaxConnectionsPerRoute(int maxConnectionsPerRoute) {
|
||||
this.maxConnectionsPerRoute = maxConnectionsPerRoute;
|
||||
}
|
||||
|
||||
public long getTimeToLive() {
|
||||
return timeToLive;
|
||||
}
|
||||
|
||||
public void setTimeToLive(long timeToLive) {
|
||||
this.timeToLive = timeToLive;
|
||||
}
|
||||
|
||||
public TimeUnit getTimeToLiveUnit() {
|
||||
return timeToLiveUnit;
|
||||
}
|
||||
|
||||
public void setTimeToLiveUnit(TimeUnit timeToLiveUnit) {
|
||||
this.timeToLiveUnit = timeToLiveUnit;
|
||||
}
|
||||
|
||||
public boolean isFollowRedirects() {
|
||||
return followRedirects;
|
||||
}
|
||||
|
||||
public void setFollowRedirects(boolean followRedirects) {
|
||||
this.followRedirects = followRedirects;
|
||||
}
|
||||
|
||||
public int getConnectionTimeout() {
|
||||
return connectionTimeout;
|
||||
}
|
||||
|
||||
public void setConnectionTimeout(int connectionTimeout) {
|
||||
this.connectionTimeout = connectionTimeout;
|
||||
}
|
||||
}
|
||||
-2
@@ -14,14 +14,12 @@ import org.springframework.util.MultiValueMap;
|
||||
import feign.FeignException;
|
||||
import feign.Response;
|
||||
import feign.codec.Decoder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Decoder adds compatibility for Spring MVC's ResponseEntity to any other decoder via
|
||||
* composition.
|
||||
* @author chadjaros
|
||||
*/
|
||||
@Slf4j
|
||||
public class ResponseEntityDecoder implements Decoder {
|
||||
|
||||
private Decoder decoder;
|
||||
|
||||
+5
-3
@@ -22,6 +22,7 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.lang.reflect.WildcardType;
|
||||
|
||||
import org.springframework.beans.factory.ObjectFactory;
|
||||
import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
|
||||
@@ -47,9 +48,10 @@ public class SpringDecoder implements Decoder {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object decode(final Response response, Type type) throws IOException,
|
||||
FeignException {
|
||||
if (type instanceof Class || type instanceof ParameterizedType) {
|
||||
public Object decode(final Response response, Type type)
|
||||
throws IOException, FeignException {
|
||||
if (type instanceof Class || type instanceof ParameterizedType
|
||||
|| type instanceof WildcardType) {
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
HttpMessageConverterExtractor<?> extractor = new HttpMessageConverterExtractor(
|
||||
type, this.messageConverters.getObject().getConverters());
|
||||
|
||||
+11
-4
@@ -23,17 +23,19 @@ import java.lang.reflect.Type;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.factory.ObjectFactory;
|
||||
import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpOutputMessage;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
|
||||
import feign.RequestTemplate;
|
||||
import feign.codec.EncodeException;
|
||||
import feign.codec.Encoder;
|
||||
import lombok.extern.apachecommons.CommonsLog;
|
||||
|
||||
import static org.springframework.cloud.netflix.feign.support.FeignUtils.getHeaders;
|
||||
import static org.springframework.cloud.netflix.feign.support.FeignUtils.getHttpHeaders;
|
||||
@@ -41,9 +43,10 @@ import static org.springframework.cloud.netflix.feign.support.FeignUtils.getHttp
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
@CommonsLog
|
||||
public class SpringEncoder implements Encoder {
|
||||
|
||||
private static final Log log = LogFactory.getLog(SpringEncoder.class);
|
||||
|
||||
private ObjectFactory<HttpMessageConverters> messageConverters;
|
||||
|
||||
public SpringEncoder(ObjectFactory<HttpMessageConverters> messageConverters) {
|
||||
@@ -95,8 +98,12 @@ public class SpringEncoder implements Encoder {
|
||||
// with the modified headers
|
||||
request.headers(getHeaders(outputMessage.getHeaders()));
|
||||
|
||||
request.body(outputMessage.getOutputStream().toByteArray(),
|
||||
Charset.forName("UTF-8")); // TODO: set charset
|
||||
// do not use charset for binary data
|
||||
if (messageConverter instanceof ByteArrayHttpMessageConverter) {
|
||||
request.body(outputMessage.getOutputStream().toByteArray(), null);
|
||||
} else {
|
||||
request.body(outputMessage.getOutputStream().toByteArray(), Charset.forName("UTF-8"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
+17
-12
@@ -18,6 +18,7 @@ package org.springframework.cloud.netflix.feign.support;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
@@ -56,6 +57,7 @@ import feign.Param;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
* @author Abhijit Sarkar
|
||||
*/
|
||||
public class SpringMvcContract extends Contract.BaseContract
|
||||
implements ResourceLoaderAware {
|
||||
@@ -153,7 +155,8 @@ public class SpringMvcContract extends Contract.BaseContract
|
||||
@Override
|
||||
protected void processAnnotationOnMethod(MethodMetadata data,
|
||||
Annotation methodAnnotation, Method method) {
|
||||
if (!(methodAnnotation instanceof RequestMapping)) {
|
||||
if (!RequestMapping.class.isInstance(methodAnnotation) && !methodAnnotation
|
||||
.annotationType().isAnnotationPresent(RequestMapping.class)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -233,11 +236,10 @@ public class SpringMvcContract extends Contract.BaseContract
|
||||
processParameterAnnotation = synthesizeWithMethodParameterNameAsFallbackValue(
|
||||
parameterAnnotation, method, paramIndex);
|
||||
isHttpAnnotation |= processor.processArgument(context,
|
||||
processParameterAnnotation);
|
||||
processParameterAnnotation, method);
|
||||
}
|
||||
}
|
||||
if (isHttpAnnotation && data.indexToExpander().get(paramIndex) == null
|
||||
&& !isMultiValued(method.getParameterTypes()[paramIndex])
|
||||
&& this.conversionService.canConvert(
|
||||
method.getParameterTypes()[paramIndex], String.class)) {
|
||||
data.indexToExpander().put(paramIndex, this.expander);
|
||||
@@ -245,13 +247,6 @@ public class SpringMvcContract extends Contract.BaseContract
|
||||
return isHttpAnnotation;
|
||||
}
|
||||
|
||||
private boolean isMultiValued(Class<?> type) {
|
||||
// Feign will deal with each element in a collection individually (with no
|
||||
// expander as of 8.16.2, but we'd rather have no conversion than convert a
|
||||
// collection to a String (which ends up being a csv).
|
||||
return Collection.class.isAssignableFrom(type);
|
||||
}
|
||||
|
||||
private void parseProduces(MethodMetadata md, Method method,
|
||||
RequestMapping annotation) {
|
||||
checkAtMostOne(method, annotation.produces(), "produces");
|
||||
@@ -280,8 +275,10 @@ public class SpringMvcContract extends Contract.BaseContract
|
||||
if (annotation.headers() != null && annotation.headers().length > 0) {
|
||||
for (String header : annotation.headers()) {
|
||||
int index = header.indexOf('=');
|
||||
md.template().header(resolve(header.substring(0, index)),
|
||||
if (!header.contains("!=") && index >= 0) {
|
||||
md.template().header(resolve(header.substring(0, index)),
|
||||
resolve(header.substring(index + 1).trim()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -313,8 +310,9 @@ public class SpringMvcContract extends Contract.BaseContract
|
||||
Object defaultValue = AnnotationUtils.getDefaultValue(parameterAnnotation);
|
||||
if (defaultValue instanceof String
|
||||
&& defaultValue.equals(annotationAttributes.get(AnnotationUtils.VALUE))) {
|
||||
Type[] parameterTypes = method.getGenericParameterTypes();
|
||||
String[] parameterNames = PARAMETER_NAME_DISCOVERER.getParameterNames(method);
|
||||
if (parameterNames != null && parameterNames.length > parameterIndex) {
|
||||
if (shouldAddParameterName(parameterIndex, parameterTypes, parameterNames)) {
|
||||
annotationAttributes.put(AnnotationUtils.VALUE,
|
||||
parameterNames[parameterIndex]);
|
||||
}
|
||||
@@ -323,6 +321,13 @@ public class SpringMvcContract extends Contract.BaseContract
|
||||
parameterAnnotation.annotationType(), null);
|
||||
}
|
||||
|
||||
private boolean shouldAddParameterName(int parameterIndex, Type[] parameterTypes, String[] parameterNames) {
|
||||
// has a parameter name
|
||||
return parameterNames != null && parameterNames.length > parameterIndex
|
||||
// has a type
|
||||
&& parameterTypes != null && parameterTypes.length > parameterIndex;
|
||||
}
|
||||
|
||||
private class SimpleAnnotatedParameterContext
|
||||
implements AnnotatedParameterProcessor.AnnotatedParameterContext {
|
||||
|
||||
|
||||
+1
-1
@@ -82,7 +82,7 @@ public class HystrixCircuitBreakerConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
public HasFeatures hystrixFeature() {
|
||||
public HasFeatures hystrixStreamFeature() {
|
||||
return HasFeatures.namedFeature("Hystrix Stream Servlet", HystrixStreamEndpoint.class);
|
||||
}
|
||||
}
|
||||
|
||||
+6
-2
@@ -19,8 +19,12 @@ package org.springframework.cloud.netflix.hystrix;
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
public interface HystrixConstants {
|
||||
public class HystrixConstants {
|
||||
|
||||
String HYSTRIX_STREAM_DESTINATION = "springCloudHystrixStream";
|
||||
public static final String HYSTRIX_STREAM_DESTINATION = "springCloudHystrixStream";
|
||||
|
||||
private HystrixConstants() {
|
||||
throw new AssertionError("Must not instantiate constant utility class");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+39
-2
@@ -18,12 +18,12 @@ package org.springframework.cloud.netflix.hystrix;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import lombok.Data;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author Venil Noronha
|
||||
* @author Gregor Zurowski
|
||||
*/
|
||||
@Data
|
||||
@ConfigurationProperties("hystrix.metrics")
|
||||
public class HystrixMetricsProperties {
|
||||
|
||||
@@ -33,4 +33,41 @@ public class HystrixMetricsProperties {
|
||||
/** Interval between subsequent polling of metrics. Defaults to 2000 ms. */
|
||||
private Integer pollingIntervalMs = 2000;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public Integer getPollingIntervalMs() {
|
||||
return pollingIntervalMs;
|
||||
}
|
||||
|
||||
public void setPollingIntervalMs(Integer pollingIntervalMs) {
|
||||
this.pollingIntervalMs = pollingIntervalMs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
HystrixMetricsProperties that = (HystrixMetricsProperties) o;
|
||||
return enabled == that.enabled &&
|
||||
Objects.equals(pollingIntervalMs, that.pollingIntervalMs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(enabled, pollingIntervalMs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new StringBuilder("HystrixMetricsProperties{")
|
||||
.append("enabled=").append(enabled).append(", ")
|
||||
.append("pollingIntervalMs=").append(pollingIntervalMs)
|
||||
.append("}").toString();
|
||||
}
|
||||
}
|
||||
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2013-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.netflix.hystrix.security;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.AllNestedConditions;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.cloud.netflix.hystrix.security.HystrixSecurityAutoConfiguration.HystrixSecurityCondition;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
|
||||
import com.netflix.hystrix.Hystrix;
|
||||
import com.netflix.hystrix.strategy.HystrixPlugins;
|
||||
import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy;
|
||||
import com.netflix.hystrix.strategy.eventnotifier.HystrixEventNotifier;
|
||||
import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook;
|
||||
import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisher;
|
||||
import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy;
|
||||
|
||||
/**
|
||||
* @author Daniel Lavoie
|
||||
*/
|
||||
@Configuration
|
||||
@Conditional(HystrixSecurityCondition.class)
|
||||
@ConditionalOnClass({ Hystrix.class, SecurityContext.class })
|
||||
public class HystrixSecurityAutoConfiguration {
|
||||
@Autowired(required = false)
|
||||
private HystrixConcurrencyStrategy existingConcurrencyStrategy;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
// Keeps references of existing Hystrix plugins.
|
||||
HystrixEventNotifier eventNotifier = HystrixPlugins.getInstance()
|
||||
.getEventNotifier();
|
||||
HystrixMetricsPublisher metricsPublisher = HystrixPlugins.getInstance()
|
||||
.getMetricsPublisher();
|
||||
HystrixPropertiesStrategy propertiesStrategy = HystrixPlugins.getInstance()
|
||||
.getPropertiesStrategy();
|
||||
HystrixCommandExecutionHook commandExecutionHook = HystrixPlugins.getInstance()
|
||||
.getCommandExecutionHook();
|
||||
|
||||
HystrixPlugins.reset();
|
||||
|
||||
// Registers existing plugins excepts the Concurrent Strategy plugin.
|
||||
HystrixPlugins.getInstance().registerConcurrencyStrategy(
|
||||
new SecurityContextConcurrencyStrategy(existingConcurrencyStrategy));
|
||||
HystrixPlugins.getInstance().registerEventNotifier(eventNotifier);
|
||||
HystrixPlugins.getInstance().registerMetricsPublisher(metricsPublisher);
|
||||
HystrixPlugins.getInstance().registerPropertiesStrategy(propertiesStrategy);
|
||||
HystrixPlugins.getInstance().registerCommandExecutionHook(commandExecutionHook);
|
||||
}
|
||||
|
||||
static class HystrixSecurityCondition extends AllNestedConditions {
|
||||
|
||||
public HystrixSecurityCondition() {
|
||||
super(ConfigurationPhase.REGISTER_BEAN);
|
||||
}
|
||||
|
||||
@ConditionalOnProperty(name = "hystrix.shareSecurityContext")
|
||||
static class ShareSecurityContext {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2013-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.netflix.hystrix.security;
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.netflix.hystrix.HystrixThreadPoolProperties;
|
||||
import org.springframework.security.concurrent.DelegatingSecurityContextCallable;
|
||||
|
||||
import com.netflix.hystrix.HystrixThreadPoolKey;
|
||||
import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy;
|
||||
import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariable;
|
||||
import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariableLifecycle;
|
||||
import com.netflix.hystrix.strategy.properties.HystrixProperty;
|
||||
|
||||
/**
|
||||
* @author daniellavoie
|
||||
*/
|
||||
public class SecurityContextConcurrencyStrategy extends HystrixConcurrencyStrategy {
|
||||
private HystrixConcurrencyStrategy existingConcurrencyStrategy;
|
||||
|
||||
public SecurityContextConcurrencyStrategy(
|
||||
HystrixConcurrencyStrategy existingConcurrencyStrategy) {
|
||||
this.existingConcurrencyStrategy = existingConcurrencyStrategy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockingQueue<Runnable> getBlockingQueue(int maxQueueSize) {
|
||||
return existingConcurrencyStrategy != null
|
||||
? existingConcurrencyStrategy.getBlockingQueue(maxQueueSize)
|
||||
: super.getBlockingQueue(maxQueueSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> HystrixRequestVariable<T> getRequestVariable(
|
||||
HystrixRequestVariableLifecycle<T> rv) {
|
||||
return existingConcurrencyStrategy != null
|
||||
? existingConcurrencyStrategy.getRequestVariable(rv)
|
||||
: super.getRequestVariable(rv);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey,
|
||||
HystrixProperty<Integer> corePoolSize,
|
||||
HystrixProperty<Integer> maximumPoolSize,
|
||||
HystrixProperty<Integer> keepAliveTime, TimeUnit unit,
|
||||
BlockingQueue<Runnable> workQueue) {
|
||||
return existingConcurrencyStrategy != null
|
||||
? existingConcurrencyStrategy.getThreadPool(threadPoolKey, corePoolSize,
|
||||
maximumPoolSize, keepAliveTime, unit, workQueue)
|
||||
: super.getThreadPool(threadPoolKey, corePoolSize, maximumPoolSize,
|
||||
keepAliveTime, unit, workQueue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties threadPoolProperties) {
|
||||
return existingConcurrencyStrategy != null
|
||||
? existingConcurrencyStrategy.getThreadPool(threadPoolKey, threadPoolProperties)
|
||||
: super.getThreadPool(threadPoolKey, threadPoolProperties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Callable<T> wrapCallable(Callable<T> callable) {
|
||||
return existingConcurrencyStrategy != null
|
||||
? existingConcurrencyStrategy
|
||||
.wrapCallable(new DelegatingSecurityContextCallable<T>(callable))
|
||||
: super.wrapCallable(new DelegatingSecurityContextCallable<T>(callable));
|
||||
}
|
||||
}
|
||||
+28
-18
@@ -1,19 +1,23 @@
|
||||
/*
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.netflix.metrics;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -25,8 +29,6 @@ import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
||||
/**
|
||||
* @author Jon Schneider
|
||||
*/
|
||||
@@ -49,14 +51,19 @@ public class DefaultMetricsTagProvider implements MetricsTagProvider {
|
||||
}
|
||||
|
||||
String host = request.getURI().getHost();
|
||||
|
||||
if( host == null ) {
|
||||
host = "none";
|
||||
}
|
||||
|
||||
String strippedUrlTemplate = urlTemplate.replaceAll("^https?://[^/]+/", "");
|
||||
//@formatter:off
|
||||
return ImmutableMap.of("method", request.getMethod().name(),
|
||||
"uri", sanitizeUrlTemplate(strippedUrlTemplate),
|
||||
"status", status, "clientName",
|
||||
host != null ? host : "none");
|
||||
//@formatter:on
|
||||
|
||||
Map<String, String> tags = new HashMap<>();
|
||||
tags.put("method", request.getMethod().name());
|
||||
tags.put("uri", sanitizeUrlTemplate(strippedUrlTemplate));
|
||||
tags.put("status", status);
|
||||
tags.put("clientName", host);
|
||||
|
||||
return Collections.unmodifiableMap(tags);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -94,8 +101,11 @@ public class DefaultMetricsTagProvider implements MetricsTagProvider {
|
||||
* As is, the urlTemplate is not suitable for use with Atlas, as all interactions with
|
||||
* Atlas take place via query parameters
|
||||
*/
|
||||
private String sanitizeUrlTemplate(String urlTemplate) {
|
||||
String sanitized = urlTemplate.replaceAll("/", "_").replaceAll("[{}]", "-");
|
||||
protected String sanitizeUrlTemplate(String urlTemplate) {
|
||||
String sanitized = urlTemplate
|
||||
.replaceAll("\\{(\\w+):.+}(?=/|$)", "-$1-") // extract path variable names from regex expressions
|
||||
.replaceAll("/", "_")
|
||||
.replaceAll("[{}]", "-");
|
||||
if (!StringUtils.hasText(sanitized)) {
|
||||
sanitized = "none";
|
||||
}
|
||||
|
||||
+14
-15
@@ -18,15 +18,12 @@ import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.cloud.netflix.metrics.servo.ServoMonitorCache;
|
||||
import org.springframework.http.HttpRequest;
|
||||
import org.springframework.http.client.ClientHttpRequestExecution;
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
|
||||
import com.netflix.servo.MonitorRegistry;
|
||||
import com.netflix.servo.monitor.MonitorConfig;
|
||||
import com.netflix.servo.tag.SmallTagMap;
|
||||
import com.netflix.servo.tag.Tags;
|
||||
@@ -43,17 +40,19 @@ public class MetricsClientHttpRequestInterceptor implements ClientHttpRequestInt
|
||||
* Boot (Actuator) provides a more general purpose abstraction for dimensional metrics
|
||||
* systems, this can be moved there and rewritten against that abstraction.
|
||||
*/
|
||||
@Autowired
|
||||
MonitorRegistry registry;
|
||||
private final ServoMonitorCache servoMonitorCache;
|
||||
|
||||
@Autowired
|
||||
Collection<MetricsTagProvider> tagProviders;
|
||||
private final Collection<MetricsTagProvider> tagProviders;
|
||||
|
||||
@Autowired
|
||||
ServoMonitorCache servoMonitorCache;
|
||||
private final String metricName;
|
||||
|
||||
@Value("${netflix.metrics.restClient.metricName:restclient}")
|
||||
String metricName;
|
||||
public MetricsClientHttpRequestInterceptor(
|
||||
Collection<MetricsTagProvider> tagProviders,
|
||||
ServoMonitorCache servoMonitorCache, String metricName) {
|
||||
this.tagProviders = tagProviders;
|
||||
this.servoMonitorCache = servoMonitorCache;
|
||||
this.metricName = metricName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
|
||||
@@ -68,8 +67,8 @@ public class MetricsClientHttpRequestInterceptor implements ClientHttpRequestInt
|
||||
finally {
|
||||
SmallTagMap.Builder builder = SmallTagMap.builder();
|
||||
for (MetricsTagProvider tagProvider : tagProviders) {
|
||||
for (Map.Entry<String, String> tag : tagProvider.clientHttpRequestTags(
|
||||
request, response).entrySet()) {
|
||||
for (Map.Entry<String, String> tag : tagProvider
|
||||
.clientHttpRequestTags(request, response).entrySet()) {
|
||||
builder.add(Tags.newTag(tag.getKey(), tag.getValue()));
|
||||
}
|
||||
}
|
||||
@@ -78,8 +77,8 @@ public class MetricsClientHttpRequestInterceptor implements ClientHttpRequestInt
|
||||
.builder(metricName);
|
||||
monitorConfigBuilder.withTags(builder);
|
||||
|
||||
servoMonitorCache.getTimer(monitorConfigBuilder.build()).record(
|
||||
System.nanoTime() - startTime, TimeUnit.NANOSECONDS);
|
||||
servoMonitorCache.getTimer(monitorConfigBuilder.build())
|
||||
.record(System.nanoTime() - startTime, TimeUnit.NANOSECONDS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+28
-9
@@ -13,22 +13,30 @@
|
||||
|
||||
package org.springframework.cloud.netflix.metrics;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.boot.actuate.metrics.reader.MetricReader;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.cloud.netflix.metrics.servo.ServoMonitorCache;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
|
||||
|
||||
import com.netflix.servo.MonitorRegistry;
|
||||
import com.netflix.servo.monitor.Monitors;
|
||||
|
||||
/**
|
||||
@@ -41,6 +49,7 @@ public class MetricsInterceptorConfiguration {
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnWebApplication
|
||||
@ConditionalOnClass(WebMvcConfigurerAdapter.class)
|
||||
static class MetricsWebResourceConfiguration extends WebMvcConfigurerAdapter {
|
||||
@Bean
|
||||
MetricsHandlerInterceptor servoMonitoringWebResourceInterceptor() {
|
||||
@@ -54,9 +63,8 @@ public class MetricsInterceptorConfiguration {
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnClass(JoinPoint.class)
|
||||
@ConditionalOnClass({ RestTemplate.class, JoinPoint.class })
|
||||
@ConditionalOnProperty(value = "spring.aop.enabled", havingValue = "true", matchIfMissing = true)
|
||||
@ConditionalOnBean({ RestTemplate.class })
|
||||
static class MetricsRestTemplateAspectConfiguration {
|
||||
|
||||
@Bean
|
||||
@@ -67,12 +75,18 @@ public class MetricsInterceptorConfiguration {
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnBean({ RestTemplate.class })
|
||||
@ConditionalOnClass({ RestTemplate.class, HttpServletRequest.class }) // HttpServletRequest implicitly required by MetricsTagProvider
|
||||
static class MetricsRestTemplateConfiguration {
|
||||
|
||||
@Value("${netflix.metrics.restClient.metricName:restclient}")
|
||||
String metricName;
|
||||
|
||||
@Bean
|
||||
MetricsClientHttpRequestInterceptor spectatorLoggingClientHttpRequestInterceptor() {
|
||||
return new MetricsClientHttpRequestInterceptor();
|
||||
MetricsClientHttpRequestInterceptor spectatorLoggingClientHttpRequestInterceptor(
|
||||
Collection<MetricsTagProvider> tagProviders,
|
||||
ServoMonitorCache servoMonitorCache) {
|
||||
return new MetricsClientHttpRequestInterceptor(tagProviders,
|
||||
servoMonitorCache, this.metricName);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -80,8 +94,8 @@ public class MetricsInterceptorConfiguration {
|
||||
return new MetricsInterceptorPostProcessor();
|
||||
}
|
||||
|
||||
private static class MetricsInterceptorPostProcessor implements
|
||||
BeanPostProcessor, ApplicationContextAware {
|
||||
private static class MetricsInterceptorPostProcessor
|
||||
implements BeanPostProcessor, ApplicationContextAware {
|
||||
private ApplicationContext context;
|
||||
private MetricsClientHttpRequestInterceptor interceptor;
|
||||
|
||||
@@ -97,7 +111,12 @@ public class MetricsInterceptorConfiguration {
|
||||
this.interceptor = this.context
|
||||
.getBean(MetricsClientHttpRequestInterceptor.class);
|
||||
}
|
||||
((RestTemplate) bean).getInterceptors().add(interceptor);
|
||||
RestTemplate restTemplate = (RestTemplate) bean;
|
||||
// create a new list as the old one may be unmodifiable (ie Arrays.asList())
|
||||
ArrayList<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
|
||||
interceptors.add(interceptor);
|
||||
interceptors.addAll(restTemplate.getInterceptors());
|
||||
restTemplate.setInterceptors(interceptors);
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ import org.aspectj.lang.annotation.Aspect;
|
||||
@Aspect
|
||||
public class RestTemplateUrlTemplateCapturingAspect {
|
||||
@Around("execution(* org.springframework.web.client.RestOperations+.*(String, ..))")
|
||||
Object captureUrlTemplate(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
public Object captureUrlTemplate(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
try {
|
||||
String urlTemplate = (String) joinPoint.getArgs()[0];
|
||||
RestTemplateUrlTemplateHolder.setRestTemplateUrlTemplate(urlTemplate);
|
||||
|
||||
+5
@@ -19,6 +19,8 @@ package org.springframework.cloud.netflix.metrics;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.env.EnvironmentPostProcessor;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
@@ -32,11 +34,14 @@ import org.springframework.util.ClassUtils;
|
||||
*/
|
||||
public class ServoEnvironmentPostProcessor implements EnvironmentPostProcessor {
|
||||
|
||||
private static final Log log = LogFactory.getLog(ServoEnvironmentPostProcessor.class);
|
||||
|
||||
@Override
|
||||
public void postProcessEnvironment(ConfigurableEnvironment environment,
|
||||
SpringApplication application) {
|
||||
if (ClassUtils.isPresent("com.netflix.servo.monitor.Monitors", null)) {
|
||||
// Make spring AOP default to target class so RestTemplates can be customized
|
||||
log.debug("Setting 'spring.aop.proxyTargetClass=true' to make spring AOP default to target class so RestTemplates can be customized");
|
||||
addDefaultProperty(environment, "spring.aop.proxyTargetClass", "true");
|
||||
}
|
||||
}
|
||||
|
||||
+51
-19
@@ -26,7 +26,9 @@ import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.client.HttpClientErrorException;
|
||||
import org.springframework.web.client.RestClientException;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
@@ -36,6 +38,7 @@ import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.dataformat.smile.SmileFactory;
|
||||
import com.netflix.servo.Metric;
|
||||
import com.netflix.servo.annotations.DataSourceType;
|
||||
import com.netflix.servo.monitor.MonitorConfig;
|
||||
import com.netflix.servo.publish.MetricObserver;
|
||||
import com.netflix.servo.tag.BasicTag;
|
||||
import com.netflix.servo.tag.Tag;
|
||||
@@ -94,7 +97,7 @@ public class AtlasMetricObserver implements MetricObserver {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected static String normalizeAtlasUri(String uri) {
|
||||
static String normalizeAtlasUri(String uri) {
|
||||
if (uri != null) {
|
||||
Matcher matcher = Pattern.compile("(.+?)(/api/v1/publish)?/?").matcher(uri);
|
||||
if (matcher.matches())
|
||||
@@ -117,7 +120,7 @@ public class AtlasMetricObserver implements MetricObserver {
|
||||
return;
|
||||
}
|
||||
|
||||
List<Metric> metrics = addTypeTagsAsNecessary(rawMetrics);
|
||||
List<Metric> metrics = sanitizeTags(addTypeTagsAsNecessary(rawMetrics));
|
||||
|
||||
for (int i = 0; i < metrics.size(); i += config.getBatchSize()) {
|
||||
List<Metric> batch = metrics.subList(i,
|
||||
@@ -127,7 +130,11 @@ public class AtlasMetricObserver implements MetricObserver {
|
||||
}
|
||||
}
|
||||
|
||||
private void sendMetricsBatch(List<Metric> metrics) {
|
||||
enum PublishMetricsBatchStatus {
|
||||
NothingToDo, Success, PartialSuccess, Failure
|
||||
}
|
||||
|
||||
PublishMetricsBatchStatus sendMetricsBatch(List<Metric> metrics) {
|
||||
try {
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
JsonGenerator gen = smileFactory.createGenerator(output, JsonEncoding.UTF8);
|
||||
@@ -136,7 +143,7 @@ public class AtlasMetricObserver implements MetricObserver {
|
||||
|
||||
writeCommonTags(gen);
|
||||
if (writeMetrics(gen, metrics) == 0)
|
||||
return; // short circuit this batch if no valid/numeric metrics existed
|
||||
return PublishMetricsBatchStatus.NothingToDo; // short circuit this batch if no valid/numeric metrics existed
|
||||
|
||||
gen.writeEndObject();
|
||||
gen.flush();
|
||||
@@ -145,22 +152,32 @@ public class AtlasMetricObserver implements MetricObserver {
|
||||
headers.setContentType(MediaType.valueOf("application/x-jackson-smile"));
|
||||
HttpEntity<byte[]> entity = new HttpEntity<>(output.toByteArray(), headers);
|
||||
try {
|
||||
restTemplate.exchange(uri, HttpMethod.POST, entity, Map.class);
|
||||
ResponseEntity<Map> response = restTemplate.exchange(uri, HttpMethod.POST, entity, Map.class);
|
||||
if(response.getStatusCode() == HttpStatus.ACCEPTED) {
|
||||
// partial success processing the metrics batch
|
||||
List<String> messages = (List<String>) response.getBody().get("message");
|
||||
if(messages != null) {
|
||||
for (String message : messages) {
|
||||
logger.error("Failed to write metric to atlas: " + message);
|
||||
}
|
||||
}
|
||||
return PublishMetricsBatchStatus.PartialSuccess;
|
||||
}
|
||||
}
|
||||
catch (HttpClientErrorException e) {
|
||||
logger.error(
|
||||
"Failed to write metrics to atlas: "
|
||||
+ e.getResponseBodyAsString(), e);
|
||||
logger.error("Failed to write metrics to atlas: " + e.getResponseBodyAsString());
|
||||
return PublishMetricsBatchStatus.Failure;
|
||||
}
|
||||
catch (RestClientException e) {
|
||||
logger.error("Failed to write metrics to atlas", e);
|
||||
return PublishMetricsBatchStatus.Failure;
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
// an IOException stemming from the generator writing to a
|
||||
// ByteArrayOutputStream is impossible
|
||||
throw new RuntimeException(e);
|
||||
return PublishMetricsBatchStatus.Failure;
|
||||
}
|
||||
|
||||
return PublishMetricsBatchStatus.Success;
|
||||
}
|
||||
|
||||
private void writeCommonTags(JsonGenerator gen) throws IOException {
|
||||
@@ -201,15 +218,32 @@ public class AtlasMetricObserver implements MetricObserver {
|
||||
return totalMetricsInBatch;
|
||||
}
|
||||
|
||||
protected static List<Metric> addTypeTagsAsNecessary(List<Metric> metrics) {
|
||||
List<Metric> typedMetrics = new ArrayList<>();
|
||||
static List<Metric> sanitizeTags(List<Metric> metrics) {
|
||||
List<Metric> sanitized = new ArrayList<>(metrics.size());
|
||||
for (Metric m : metrics) {
|
||||
MonitorConfig.Builder config = MonitorConfig.builder(toValidCharset(m.getConfig().getName()));
|
||||
for (Tag tag : m.getConfig().getTags()) {
|
||||
config.withTag(toValidCharset(tag.getKey()), toValidCharset(tag.getValue()));
|
||||
}
|
||||
config.withPublishingPolicy(m.getConfig().getPublishingPolicy());
|
||||
sanitized.add(new Metric(config.build(), m.getTimestamp(), m.getValue()));
|
||||
}
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
private static String toValidCharset(String name) {
|
||||
return name.replaceAll("[^\\.\\-\\w]", "_");
|
||||
}
|
||||
|
||||
static List<Metric> addTypeTagsAsNecessary(List<Metric> metrics) {
|
||||
List<Metric> typedMetrics = new ArrayList<>(metrics.size());
|
||||
for (Metric m : metrics) {
|
||||
String value = m.getConfig().getTags().getValue(DataSourceType.KEY);
|
||||
Metric transformed;
|
||||
|
||||
// Atlas will not normalize metrics tagged with atlas.dstype=gauge. Since
|
||||
// these metric types are
|
||||
// pre-normalized, we do not want Atlas to touch the value
|
||||
// these metric types are pre-normalized, we do not want Atlas to touch the
|
||||
// value
|
||||
if (DataSourceType.GAUGE.name().equals(value)
|
||||
|| DataSourceType.RATE.name().equals(value)
|
||||
|| DataSourceType.NORMALIZED.name().equals(value)) {
|
||||
@@ -218,10 +252,8 @@ public class AtlasMetricObserver implements MetricObserver {
|
||||
}
|
||||
|
||||
// atlas.dstype=counter means you're sending the absolute value of the counter
|
||||
// (a monotonically
|
||||
// increasing value), and Atlas will keep the previous value and convert it to
|
||||
// a rate per second
|
||||
// when the metric is received
|
||||
// (a monotonically increasing value), and Atlas will keep the previous value
|
||||
// and convert it to a rate per second when the metric is received
|
||||
else if (DataSourceType.COUNTER.name().equals(value)) {
|
||||
transformed = new Metric(
|
||||
m.getConfig().withAdditionalTag(atlasCounterTag),
|
||||
|
||||
+2
-2
@@ -16,7 +16,7 @@ package org.springframework.cloud.netflix.metrics.servo;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.netflix.servo.monitor.Monitor;
|
||||
import com.netflix.servo.monitor.MonitorConfig;
|
||||
@@ -33,6 +33,6 @@ public class DimensionalServoMetricNaming implements ServoMetricNaming {
|
||||
for (Tag t : config.getTags()) {
|
||||
tags.add(t.getKey() + "=" + t.getValue());
|
||||
}
|
||||
return config.getName() + "(" + StringUtils.join(tags, ",") + ")";
|
||||
return config.getName() + "(" + StringUtils.collectionToCommaDelimitedString(tags) + ")";
|
||||
}
|
||||
}
|
||||
|
||||
+7
-3
@@ -95,8 +95,12 @@ public class ServoMetricsAutoConfiguration {
|
||||
return new ServoMetricServices(monitorRegistry);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MetricsTagProvider defaultMetricsTagProvider() {
|
||||
return new DefaultMetricsTagProvider();
|
||||
@Configuration
|
||||
@ConditionalOnClass(name = "javax.servlet.http.HttpServletRequest")
|
||||
protected static class MetricsTagConfiguration {
|
||||
@Bean
|
||||
public MetricsTagProvider defaultMetricsTagProvider() {
|
||||
return new DefaultMetricsTagProvider();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
-3
@@ -19,16 +19,18 @@ import java.util.Map;
|
||||
import com.netflix.servo.MonitorRegistry;
|
||||
import com.netflix.servo.monitor.BasicTimer;
|
||||
import com.netflix.servo.monitor.MonitorConfig;
|
||||
|
||||
import lombok.extern.apachecommons.CommonsLog;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* Servo does not provide a mechanism to retrieve an existing monitor by name + tags.
|
||||
*
|
||||
* @author Jon Schneider
|
||||
*/
|
||||
@CommonsLog
|
||||
public class ServoMonitorCache {
|
||||
|
||||
private static final Log log = LogFactory.getLog(ServoMonitorCache.class);
|
||||
|
||||
private final Map<MonitorConfig, BasicTimer> timerCache = new HashMap<>();
|
||||
private final MonitorRegistry monitorRegistry;
|
||||
private final ServoMetricsConfigBean config;
|
||||
|
||||
+16
-5
@@ -16,19 +16,30 @@
|
||||
|
||||
package org.springframework.cloud.netflix.ribbon;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import com.netflix.loadbalancer.Server;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
public class DefaultServerIntrospector implements ServerIntrospector {
|
||||
|
||||
private ServerIntrospectorProperties serverIntrospectorProperties = new ServerIntrospectorProperties();
|
||||
|
||||
@Autowired(required = false)
|
||||
public void setServerIntrospectorProperties(ServerIntrospectorProperties serverIntrospectorProperties){
|
||||
this.serverIntrospectorProperties = serverIntrospectorProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSecure(Server server) {
|
||||
// Can we do better?
|
||||
return (""+server.getPort()).endsWith("443");
|
||||
return serverIntrospectorProperties.getSecurePorts().contains(server.getPort());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
package org.springframework.cloud.netflix.ribbon;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.netflix.client.config.IClientConfig;
|
||||
import com.netflix.loadbalancer.ILoadBalancer;
|
||||
import com.netflix.loadbalancer.IPing;
|
||||
import com.netflix.loadbalancer.IRule;
|
||||
import com.netflix.loadbalancer.ServerList;
|
||||
import com.netflix.loadbalancer.ServerListFilter;
|
||||
|
||||
import static org.springframework.cloud.netflix.ribbon.SpringClientFactory.NAMESPACE;
|
||||
import static org.springframework.cloud.netflix.ribbon.SpringClientFactory.instantiateWithConfig;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
public class PropertiesFactory {
|
||||
@Autowired
|
||||
private Environment environment;
|
||||
|
||||
private Map<Class, String> classToProperty = new HashMap<>();
|
||||
|
||||
public PropertiesFactory() {
|
||||
classToProperty.put(ILoadBalancer.class, "NFLoadBalancerClassName");
|
||||
classToProperty.put(IPing.class, "NFLoadBalancerPingClassName");
|
||||
classToProperty.put(IRule.class, "NFLoadBalancerRuleClassName");
|
||||
classToProperty.put(ServerList.class, "NIWSServerListClassName");
|
||||
classToProperty.put(ServerListFilter.class, "NIWSServerListFilterClassName");
|
||||
}
|
||||
|
||||
public boolean isSet(Class clazz, String name) {
|
||||
return StringUtils.hasText(getClassName(clazz, name));
|
||||
}
|
||||
|
||||
public String getClassName(Class clazz, String name) {
|
||||
if (this.classToProperty.containsKey(clazz)) {
|
||||
String classNameProperty = this.classToProperty.get(clazz);
|
||||
String className = environment.getProperty(name + "." + NAMESPACE + "." + classNameProperty);
|
||||
return className;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <C> C get(Class<C> clazz, IClientConfig config, String name) {
|
||||
String className = getClassName(clazz, name);
|
||||
if (StringUtils.hasText(className)) {
|
||||
try {
|
||||
Class<?> toInstantiate = Class.forName(className);
|
||||
return (C) instantiateWithConfig(toInstantiate, config);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new IllegalArgumentException("Unknown class to load "+className+" for class " + clazz + " named " + name);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.netflix.ribbon;
|
||||
|
||||
import com.netflix.client.AbstractLoadBalancerAwareClient;
|
||||
import com.netflix.client.RetryHandler;
|
||||
import com.netflix.client.config.IClientConfig;
|
||||
import com.netflix.loadbalancer.ILoadBalancer;
|
||||
import com.netflix.niws.client.http.RestClient;
|
||||
import com.netflix.servo.monitor.Monitors;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
@Configuration
|
||||
@RibbonAutoConfiguration.ConditionalOnRibbonRestClient
|
||||
class RestClientRibbonConfiguration {
|
||||
@Value("${ribbon.client.name}")
|
||||
private String name = "client";
|
||||
|
||||
/**
|
||||
* Create a Netflix {@link RestClient} integrated with Ribbon if none already exists
|
||||
* in the application context. It is not required for Ribbon to work properly and is
|
||||
* therefore created lazily if ever another component requires it.
|
||||
*
|
||||
* @param config the configuration to use by the underlying Ribbon instance
|
||||
* @param loadBalancer the load balancer to use by the underlying Ribbon instance
|
||||
* @param serverIntrospector server introspector to use by the underlying Ribbon instance
|
||||
* @param retryHandler retry handler to use by the underlying Ribbon instance
|
||||
* @return a {@link RestClient} instances backed by Ribbon
|
||||
*/
|
||||
@Bean
|
||||
@Lazy
|
||||
@ConditionalOnMissingBean(AbstractLoadBalancerAwareClient.class)
|
||||
public RestClient ribbonRestClient(IClientConfig config, ILoadBalancer loadBalancer,
|
||||
ServerIntrospector serverIntrospector, RetryHandler retryHandler) {
|
||||
RestClient client = new RibbonClientConfiguration.OverrideRestClient(config, serverIntrospector);
|
||||
client.setLoadBalancer(loadBalancer);
|
||||
client.setRetryHandler(retryHandler);
|
||||
Monitors.registerObject("Client_" + this.name, client);
|
||||
return client;
|
||||
}
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.netflix.ribbon;
|
||||
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Responsible for eagerly creating the child application context holding the Ribbon
|
||||
* related configuration
|
||||
*
|
||||
* @author Biju Kunjummen
|
||||
*/
|
||||
public class RibbonApplicationContextInitializer
|
||||
implements ApplicationListener<ApplicationReadyEvent> {
|
||||
|
||||
private final SpringClientFactory springClientFactory;
|
||||
|
||||
//List of Ribbon client names
|
||||
private final List<String> clientNames;
|
||||
|
||||
public RibbonApplicationContextInitializer(SpringClientFactory springClientFactory,
|
||||
List<String> clientNames) {
|
||||
this.springClientFactory = springClientFactory;
|
||||
this.clientNames = clientNames;
|
||||
}
|
||||
|
||||
private void initialize() {
|
||||
if (clientNames != null) {
|
||||
for (String clientName : clientNames) {
|
||||
this.springClientFactory.getContext(clientName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ApplicationReadyEvent event) {
|
||||
initialize();
|
||||
}
|
||||
}
|
||||
+84
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,21 +16,35 @@
|
||||
|
||||
package org.springframework.cloud.netflix.ribbon;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.client.actuator.HasFeatures;
|
||||
import org.springframework.cloud.client.loadbalancer.AsyncLoadBalancerAutoConfiguration;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryListenerFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
|
||||
import org.springframework.cloud.client.loadbalancer.RestTemplateCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.client.AsyncRestTemplate;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import com.netflix.client.IClient;
|
||||
@@ -42,16 +56,21 @@ import com.netflix.ribbon.Ribbon;
|
||||
*
|
||||
* @author Spencer Gibb
|
||||
* @author Dave Syer
|
||||
* @author Biju Kunjummen
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnClass({ IClient.class, RestTemplate.class })
|
||||
@ConditionalOnClass({ IClient.class, RestTemplate.class, AsyncRestTemplate.class, Ribbon.class})
|
||||
@RibbonClients
|
||||
@AutoConfigureAfter(name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
|
||||
@AutoConfigureBefore(LoadBalancerAutoConfiguration.class)
|
||||
@AutoConfigureBefore({LoadBalancerAutoConfiguration.class, AsyncLoadBalancerAutoConfiguration.class})
|
||||
@EnableConfigurationProperties({RibbonEagerLoadProperties.class, ServerIntrospectorProperties.class})
|
||||
public class RibbonAutoConfiguration {
|
||||
|
||||
@Autowired(required = false)
|
||||
private List<RibbonClientSpecification> configurations = new ArrayList<>();
|
||||
|
||||
@Autowired
|
||||
private RibbonEagerLoadProperties ribbonEagerLoadProperties;
|
||||
|
||||
@Bean
|
||||
public HasFeatures ribbonFeature() {
|
||||
@@ -71,9 +90,50 @@ public class RibbonAutoConfiguration {
|
||||
return new RibbonLoadBalancerClient(springClientFactory());
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
|
||||
@ConditionalOnMissingBean
|
||||
public LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory(SpringClientFactory clientFactory) {
|
||||
return new RibbonLoadBalancedRetryPolicyFactory(clientFactory);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingClass(value = "org.springframework.retry.support.RetryTemplate")
|
||||
@ConditionalOnMissingBean
|
||||
public LoadBalancedRetryPolicyFactory neverRetryPolicyFactory() {
|
||||
return new LoadBalancedRetryPolicyFactory.NeverRetryFactory();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
|
||||
@ConditionalOnMissingBean
|
||||
public LoadBalancedBackOffPolicyFactory loadBalancedBackoffPolicyFactory() {
|
||||
return new LoadBalancedBackOffPolicyFactory.NoBackOffPolicyFactory();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
|
||||
@ConditionalOnMissingBean
|
||||
public LoadBalancedRetryListenerFactory loadBalancedRetryListenerFactory() {
|
||||
return new LoadBalancedRetryListenerFactory.DefaultRetryListenerFactory();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public PropertiesFactory propertiesFactory() {
|
||||
return new PropertiesFactory();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(value = "ribbon.eager-load.enabled", matchIfMissing = false)
|
||||
public RibbonApplicationContextInitializer ribbonApplicationContextInitializer() {
|
||||
return new RibbonApplicationContextInitializer(springClientFactory(),
|
||||
ribbonEagerLoadProperties.getClients());
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnClass(HttpRequest.class)
|
||||
@ConditionalOnProperty(value = "ribbon.http.client.enabled", matchIfMissing = false)
|
||||
@ConditionalOnRibbonRestClient
|
||||
protected static class RibbonClientConfig {
|
||||
|
||||
@Autowired
|
||||
@@ -96,4 +156,24 @@ public class RibbonAutoConfiguration {
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: support for autoconfiguring restemplate to use apache http client or okhttp
|
||||
|
||||
@Target({ ElementType.TYPE, ElementType.METHOD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Conditional(OnRibbonRestClientCondition.class)
|
||||
@interface ConditionalOnRibbonRestClient { }
|
||||
|
||||
private static class OnRibbonRestClientCondition extends AnyNestedCondition {
|
||||
public OnRibbonRestClientCondition() {
|
||||
super(ConfigurationPhase.REGISTER_BEAN);
|
||||
}
|
||||
|
||||
@Deprecated //remove in Edgware"
|
||||
@ConditionalOnProperty("ribbon.http.client.enabled")
|
||||
static class ZuulProperty {}
|
||||
|
||||
@ConditionalOnProperty("ribbon.restclient.enabled")
|
||||
static class RibbonProperty {}
|
||||
}
|
||||
}
|
||||
|
||||
+58
-50
@@ -22,36 +22,41 @@ import javax.annotation.PostConstruct;
|
||||
|
||||
import org.apache.http.client.params.ClientPNames;
|
||||
import org.apache.http.client.params.CookiePolicy;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.commons.httpclient.HttpClientConfiguration;
|
||||
import org.springframework.cloud.netflix.ribbon.apache.HttpClientRibbonConfiguration;
|
||||
import org.springframework.cloud.netflix.ribbon.okhttp.OkHttpRibbonConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
import com.netflix.client.DefaultLoadBalancerRetryHandler;
|
||||
import com.netflix.client.RetryHandler;
|
||||
import com.netflix.client.config.CommonClientConfigKey;
|
||||
import com.netflix.client.config.DefaultClientConfigImpl;
|
||||
import com.netflix.client.config.IClientConfig;
|
||||
import com.netflix.loadbalancer.ConfigurationBasedServerList;
|
||||
import com.netflix.loadbalancer.DummyPing;
|
||||
import com.netflix.loadbalancer.ILoadBalancer;
|
||||
import com.netflix.loadbalancer.IPing;
|
||||
import com.netflix.loadbalancer.IRule;
|
||||
import com.netflix.loadbalancer.LoadBalancerBuilder;
|
||||
import com.netflix.loadbalancer.NoOpPing;
|
||||
import com.netflix.loadbalancer.PollingServerListUpdater;
|
||||
import com.netflix.loadbalancer.Server;
|
||||
import com.netflix.loadbalancer.ServerList;
|
||||
import com.netflix.loadbalancer.ServerListFilter;
|
||||
import com.netflix.loadbalancer.ServerListUpdater;
|
||||
import com.netflix.loadbalancer.ZoneAvoidanceRule;
|
||||
import com.netflix.loadbalancer.ZoneAwareLoadBalancer;
|
||||
import com.netflix.niws.client.http.RestClient;
|
||||
import com.netflix.servo.monitor.Monitors;
|
||||
import com.sun.jersey.api.client.Client;
|
||||
import com.sun.jersey.client.apache4.ApacheHttpClient4;
|
||||
|
||||
import static com.netflix.client.config.CommonClientConfigKey.DeploymentContextBasedVipAddresses;
|
||||
import static org.springframework.cloud.netflix.ribbon.RibbonProperyUtils.setRibbonProperty;
|
||||
import static org.springframework.cloud.netflix.ribbon.RibbonUtils.setRibbonProperty;
|
||||
import static org.springframework.cloud.netflix.ribbon.RibbonUtils.updateToHttpsIfNeeded;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
@@ -59,25 +64,39 @@ import static org.springframework.cloud.netflix.ribbon.RibbonProperyUtils.setRib
|
||||
@SuppressWarnings("deprecation")
|
||||
@Configuration
|
||||
@EnableConfigurationProperties
|
||||
//Order is important here, last should be the default, first should be optional
|
||||
// see https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653
|
||||
@Import({HttpClientConfiguration.class, OkHttpRibbonConfiguration.class, RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class})
|
||||
public class RibbonClientConfiguration {
|
||||
|
||||
public static final int DEFAULT_CONNECT_TIMEOUT = 1000;
|
||||
public static final int DEFAULT_READ_TIMEOUT = 1000;
|
||||
|
||||
@Value("${ribbon.client.name}")
|
||||
private String name = "client";
|
||||
|
||||
// TODO: maybe re-instate autowired load balancers: identified by name they could be
|
||||
// associated with ribbon clients
|
||||
|
||||
@Autowired
|
||||
private PropertiesFactory propertiesFactory;
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public IClientConfig ribbonClientConfig() {
|
||||
DefaultClientConfigImpl config = new DefaultClientConfigImpl();
|
||||
config.loadProperties(this.name);
|
||||
config.set(CommonClientConfigKey.ConnectTimeout, DEFAULT_CONNECT_TIMEOUT);
|
||||
config.set(CommonClientConfigKey.ReadTimeout, DEFAULT_READ_TIMEOUT);
|
||||
return config;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public IRule ribbonRule(IClientConfig config) {
|
||||
if (this.propertiesFactory.isSet(IRule.class, name)) {
|
||||
return this.propertiesFactory.get(IRule.class, config, name);
|
||||
}
|
||||
ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
|
||||
rule.initWithNiwsConfig(config);
|
||||
return rule;
|
||||
@@ -86,56 +105,49 @@ public class RibbonClientConfiguration {
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public IPing ribbonPing(IClientConfig config) {
|
||||
// TODO: use PingUrl
|
||||
return new NoOpPing();
|
||||
if (this.propertiesFactory.isSet(IPing.class, name)) {
|
||||
return this.propertiesFactory.get(IPing.class, config, name);
|
||||
}
|
||||
return new DummyPing();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@SuppressWarnings("unchecked")
|
||||
public ServerList<Server> ribbonServerList(IClientConfig config) {
|
||||
if (this.propertiesFactory.isSet(ServerList.class, name)) {
|
||||
return this.propertiesFactory.get(ServerList.class, config, name);
|
||||
}
|
||||
ConfigurationBasedServerList serverList = new ConfigurationBasedServerList();
|
||||
serverList.initWithNiwsConfig(config);
|
||||
return serverList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Netflix {@link RestClient} integrated with Ribbon if none already exists
|
||||
* in the application context. It is not required for Ribbon to work properly and is
|
||||
* therefore created lazily if ever another component requires it.
|
||||
*
|
||||
* @param config the configuration to use by the underlying Ribbon instance
|
||||
* @param loadBalancer the load balancer to use by the underlying Ribbon instance
|
||||
* @param serverIntrospector server introspector to use by the underlying Ribbon instance
|
||||
* @param retryHandler retry handler to use by the underlying Ribbon instance
|
||||
* @return a {@link RestClient} instances backed by Ribbon
|
||||
*/
|
||||
@Bean
|
||||
@Lazy
|
||||
@ConditionalOnMissingBean
|
||||
public RestClient ribbonRestClient(IClientConfig config, ILoadBalancer loadBalancer,
|
||||
ServerIntrospector serverIntrospector, RetryHandler retryHandler) {
|
||||
RestClient client = new OverrideRestClient(config, serverIntrospector);
|
||||
client.setLoadBalancer(loadBalancer);
|
||||
client.setRetryHandler(retryHandler);
|
||||
Monitors.registerObject("Client_" + this.name, client);
|
||||
return client;
|
||||
public ServerListUpdater ribbonServerListUpdater(IClientConfig config) {
|
||||
return new PollingServerListUpdater(config);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
|
||||
ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
|
||||
IRule rule, IPing ping) {
|
||||
ZoneAwareLoadBalancer<Server> balancer = LoadBalancerBuilder.newBuilder()
|
||||
.withClientConfig(config).withRule(rule).withPing(ping)
|
||||
.withServerListFilter(serverListFilter).withDynamicServerList(serverList)
|
||||
.buildDynamicServerListLoadBalancer();
|
||||
return balancer;
|
||||
IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
|
||||
if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
|
||||
return this.propertiesFactory.get(ILoadBalancer.class, config, name);
|
||||
}
|
||||
return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
|
||||
serverListFilter, serverListUpdater);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@SuppressWarnings("unchecked")
|
||||
public ServerListFilter<Server> ribbonServerListFilter(IClientConfig config) {
|
||||
if (this.propertiesFactory.isSet(ServerListFilter.class, name)) {
|
||||
return this.propertiesFactory.get(ServerListFilter.class, config, name);
|
||||
}
|
||||
ZonePreferenceServerListFilter filter = new ZonePreferenceServerListFilter();
|
||||
filter.initWithNiwsConfig(config);
|
||||
return filter;
|
||||
@@ -143,8 +155,8 @@ public class RibbonClientConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public RibbonLoadBalancerContext ribbonLoadBalancerContext(
|
||||
ILoadBalancer loadBalancer, IClientConfig config, RetryHandler retryHandler) {
|
||||
public RibbonLoadBalancerContext ribbonLoadBalancerContext(ILoadBalancer loadBalancer,
|
||||
IClientConfig config, RetryHandler retryHandler) {
|
||||
return new RibbonLoadBalancerContext(loadBalancer, config, retryHandler);
|
||||
}
|
||||
|
||||
@@ -153,7 +165,7 @@ public class RibbonClientConfiguration {
|
||||
public RetryHandler retryHandler(IClientConfig config) {
|
||||
return new DefaultLoadBalancerRetryHandler(config);
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public ServerIntrospector serverIntrospector() {
|
||||
@@ -167,33 +179,29 @@ public class RibbonClientConfiguration {
|
||||
|
||||
static class OverrideRestClient extends RestClient {
|
||||
|
||||
private IClientConfig config;
|
||||
private ServerIntrospector serverIntrospector;
|
||||
|
||||
protected OverrideRestClient(IClientConfig ncc,
|
||||
protected OverrideRestClient(IClientConfig config,
|
||||
ServerIntrospector serverIntrospector) {
|
||||
super();
|
||||
this.config = config;
|
||||
this.serverIntrospector = serverIntrospector;
|
||||
initWithNiwsConfig(ncc);
|
||||
initWithNiwsConfig(this.config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI reconstructURIWithServer(Server server, URI original) {
|
||||
String scheme = original.getScheme();
|
||||
if (!"https".equals(scheme) && this.serverIntrospector.isSecure(server)) {
|
||||
original = UriComponentsBuilder.fromUri(original).scheme("https").build()
|
||||
.toUri();
|
||||
}
|
||||
return super.reconstructURIWithServer(server, original);
|
||||
URI uri = updateToHttpsIfNeeded(original, this.config,
|
||||
this.serverIntrospector, server);
|
||||
return super.reconstructURIWithServer(server, uri);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Client apacheHttpClientSpecificInitialization() {
|
||||
ApacheHttpClient4 apache = (ApacheHttpClient4) super
|
||||
.apacheHttpClientSpecificInitialization();
|
||||
apache.getClientHandler()
|
||||
.getHttpClient()
|
||||
.getParams()
|
||||
.setParameter(ClientPNames.COOKIE_POLICY, CookiePolicy.IGNORE_COOKIES);
|
||||
ApacheHttpClient4 apache = (ApacheHttpClient4) super.apacheHttpClientSpecificInitialization();
|
||||
apache.getClientHandler().getHttpClient().getParams().setParameter(
|
||||
ClientPNames.COOKIE_POLICY, CookiePolicy.IGNORE_COOKIES);
|
||||
return apache;
|
||||
}
|
||||
|
||||
|
||||
+48
-6
@@ -18,20 +18,62 @@ package org.springframework.cloud.netflix.ribbon;
|
||||
|
||||
import org.springframework.cloud.context.named.NamedContextFactory;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class RibbonClientSpecification implements NamedContextFactory.Specification {
|
||||
|
||||
private String name;
|
||||
|
||||
private Class<?>[] configuration;
|
||||
|
||||
public RibbonClientSpecification() {
|
||||
}
|
||||
|
||||
public RibbonClientSpecification(String name, Class<?>[] configuration) {
|
||||
this.name = name;
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Class<?>[] getConfiguration() {
|
||||
return configuration;
|
||||
}
|
||||
|
||||
public void setConfiguration(Class<?>[] configuration) {
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
RibbonClientSpecification that = (RibbonClientSpecification) o;
|
||||
return Arrays.equals(configuration, that.configuration) &&
|
||||
Objects.equals(name, that.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(configuration, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new StringBuilder("RibbonClientSpecification{")
|
||||
.append("name='").append(name).append("', ")
|
||||
.append("configuration=").append(Arrays.toString(configuration))
|
||||
.append("}").toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.netflix.ribbon;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/*
|
||||
* Configuration Properties to indicate which Ribbon configurations
|
||||
* should be eagerly loaded up
|
||||
*
|
||||
* @author Biju Kunjummen
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "ribbon.eager-load")
|
||||
public class RibbonEagerLoadProperties {
|
||||
private boolean enabled = false;
|
||||
private List<String> clients;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public List<String> getClients() {
|
||||
return clients;
|
||||
}
|
||||
|
||||
public void setClients(List<String> clients) {
|
||||
this.clients = clients;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -108,6 +108,6 @@ public class RibbonHttpRequest extends AbstractClientHttpRequest {
|
||||
}
|
||||
|
||||
private boolean isDynamic(String name) {
|
||||
return name.equalsIgnoreCase("Content-Length") || name.equalsIgnoreCase("Transfer-Encoding");
|
||||
return "Content-Length".equalsIgnoreCase(name) || "Transfer-Encoding".equalsIgnoreCase(name);
|
||||
}
|
||||
}
|
||||
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.netflix.ribbon;
|
||||
|
||||
import com.netflix.client.config.CommonClientConfigKey;
|
||||
import com.netflix.client.config.IClientConfig;
|
||||
import com.netflix.client.config.IClientConfigKey;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy;
|
||||
import org.springframework.cloud.client.loadbalancer.ServiceInstanceChooser;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* {@link LoadBalancedRetryPolicy} for Ribbon clients.
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
public class RibbonLoadBalancedRetryPolicy implements LoadBalancedRetryPolicy {
|
||||
|
||||
public static final IClientConfigKey<String> RETRYABLE_STATUS_CODES = new CommonClientConfigKey<String>("retryableStatusCodes") {};
|
||||
private int sameServerCount = 0;
|
||||
private int nextServerCount = 0;
|
||||
private String serviceId;
|
||||
private RibbonLoadBalancerContext lbContext;
|
||||
private ServiceInstanceChooser loadBalanceChooser;
|
||||
List<Integer> retryableStatusCodes = new ArrayList<>();
|
||||
|
||||
public RibbonLoadBalancedRetryPolicy(String serviceId, RibbonLoadBalancerContext context, ServiceInstanceChooser loadBalanceChooser) {
|
||||
this.serviceId = serviceId;
|
||||
this.lbContext = context;
|
||||
this.loadBalanceChooser = loadBalanceChooser;
|
||||
}
|
||||
|
||||
public RibbonLoadBalancedRetryPolicy(String serviceId, RibbonLoadBalancerContext context, ServiceInstanceChooser loadBalanceChooser,
|
||||
IClientConfig clientConfig) {
|
||||
this.serviceId = serviceId;
|
||||
this.lbContext = context;
|
||||
this.loadBalanceChooser = loadBalanceChooser;
|
||||
String retryableStatusCodesProp = clientConfig.getPropertyAsString(RETRYABLE_STATUS_CODES, "");
|
||||
String[] retryableStatusCodesArray = retryableStatusCodesProp.split(",");
|
||||
for(String code : retryableStatusCodesArray) {
|
||||
if(!StringUtils.isEmpty(code)) {
|
||||
try {
|
||||
retryableStatusCodes.add(Integer.valueOf(code.trim()));
|
||||
} catch (NumberFormatException e) {
|
||||
//TODO log
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean canRetry(LoadBalancedRetryContext context) {
|
||||
HttpMethod method = context.getRequest().getMethod();
|
||||
return HttpMethod.GET == method || lbContext.isOkToRetryOnAllOperations();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRetrySameServer(LoadBalancedRetryContext context) {
|
||||
return sameServerCount < lbContext.getRetryHandler().getMaxRetriesOnSameServer() && canRetry(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRetryNextServer(LoadBalancedRetryContext context) {
|
||||
//this will be called after a failure occurs and we increment the counter
|
||||
//so we check that the count is less than or equals to too make sure
|
||||
//we try the next server the right number of times
|
||||
return nextServerCount <= lbContext.getRetryHandler().getMaxRetriesOnNextServer() && canRetry(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close(LoadBalancedRetryContext context) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerThrowable(LoadBalancedRetryContext context, Throwable throwable) {
|
||||
//Check if we need to ask the load balancer for a new server.
|
||||
//Do this before we increment the counters because the first call to this method
|
||||
//is not a retry it is just an initial failure.
|
||||
if(!canRetrySameServer(context) && canRetryNextServer(context)) {
|
||||
context.setServiceInstance(loadBalanceChooser.choose(serviceId));
|
||||
}
|
||||
//This method is called regardless of whether we are retrying or making the first request.
|
||||
//Since we do not count the initial request in the retry count we don't reset the counter
|
||||
//until we actually equal the same server count limit. This will allow us to make the initial
|
||||
//request plus the right number of retries.
|
||||
if(sameServerCount >= lbContext.getRetryHandler().getMaxRetriesOnSameServer() && canRetry(context)) {
|
||||
//reset same server since we are moving to a new server
|
||||
sameServerCount = 0;
|
||||
nextServerCount++;
|
||||
if(!canRetryNextServer(context)) {
|
||||
context.setExhaustedOnly();
|
||||
}
|
||||
} else {
|
||||
sameServerCount++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retryableStatusCode(int statusCode) {
|
||||
return retryableStatusCodes.contains(statusCode);
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cloud.netflix.ribbon;
|
||||
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.ServiceInstanceChooser;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
public class RibbonLoadBalancedRetryPolicyFactory implements LoadBalancedRetryPolicyFactory {
|
||||
|
||||
private SpringClientFactory clientFactory;
|
||||
|
||||
public RibbonLoadBalancedRetryPolicyFactory(SpringClientFactory clientFactory) {
|
||||
this.clientFactory = clientFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoadBalancedRetryPolicy create(String serviceId, ServiceInstanceChooser loadBalanceChooser) {
|
||||
RibbonLoadBalancerContext lbContext = this.clientFactory
|
||||
.getLoadBalancerContext(serviceId);
|
||||
return new RibbonLoadBalancedRetryPolicy(serviceId, lbContext, loadBalanceChooser, clientFactory.getClientConfig(serviceId));
|
||||
}
|
||||
}
|
||||
|
||||
+24
-18
@@ -20,16 +20,12 @@ import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.cloud.client.DefaultServiceInstance;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerRequest;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import com.netflix.client.config.CommonClientConfigKey;
|
||||
import com.netflix.client.config.IClientConfig;
|
||||
import com.netflix.loadbalancer.ILoadBalancer;
|
||||
import com.netflix.loadbalancer.Server;
|
||||
@@ -37,6 +33,7 @@ import com.netflix.loadbalancer.Server;
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
* @author Dave Syer
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
public class RibbonLoadBalancerClient implements LoadBalancerClient {
|
||||
|
||||
@@ -53,11 +50,10 @@ public class RibbonLoadBalancerClient implements LoadBalancerClient {
|
||||
RibbonLoadBalancerContext context = this.clientFactory
|
||||
.getLoadBalancerContext(serviceId);
|
||||
Server server = new Server(instance.getHost(), instance.getPort());
|
||||
boolean secure = isSecure(server, serviceId);
|
||||
URI uri = original;
|
||||
if (secure) {
|
||||
uri = UriComponentsBuilder.fromUri(uri).scheme("https").build().toUri();
|
||||
}
|
||||
IClientConfig clientConfig = clientFactory.getClientConfig(serviceId);
|
||||
ServerIntrospector serverIntrospector = serverIntrospector(serviceId);
|
||||
URI uri = RibbonUtils.updateToHttpsIfNeeded(original, clientConfig,
|
||||
serverIntrospector, server);
|
||||
return context.reconstructURIWithServer(server, uri);
|
||||
}
|
||||
|
||||
@@ -81,12 +77,25 @@ public class RibbonLoadBalancerClient implements LoadBalancerClient {
|
||||
RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server,
|
||||
serviceId), serverIntrospector(serviceId).getMetadata(server));
|
||||
|
||||
return execute(serviceId, ribbonServer, request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException {
|
||||
Server server = null;
|
||||
if(serviceInstance instanceof RibbonServer) {
|
||||
server = ((RibbonServer)serviceInstance).getServer();
|
||||
}
|
||||
if (server == null) {
|
||||
throw new IllegalStateException("No instances available for " + serviceId);
|
||||
}
|
||||
|
||||
RibbonLoadBalancerContext context = this.clientFactory
|
||||
.getLoadBalancerContext(serviceId);
|
||||
RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);
|
||||
|
||||
try {
|
||||
T returnVal = request.apply(ribbonServer);
|
||||
T returnVal = request.apply(serviceInstance);
|
||||
statsRecorder.recordStats(returnVal);
|
||||
return returnVal;
|
||||
}
|
||||
@@ -113,11 +122,8 @@ public class RibbonLoadBalancerClient implements LoadBalancerClient {
|
||||
|
||||
private boolean isSecure(Server server, String serviceId) {
|
||||
IClientConfig config = this.clientFactory.getClientConfig(serviceId);
|
||||
if (config != null) {
|
||||
return config.get(CommonClientConfigKey.IsSecure, false);
|
||||
}
|
||||
|
||||
return serverIntrospector(serviceId).isSecure(server);
|
||||
ServerIntrospector serverIntrospector = serverIntrospector(serviceId);
|
||||
return RibbonUtils.isSecure(config, serverIntrospector, server);
|
||||
}
|
||||
|
||||
protected Server getServer(String serviceId) {
|
||||
@@ -135,17 +141,17 @@ public class RibbonLoadBalancerClient implements LoadBalancerClient {
|
||||
return this.clientFactory.getLoadBalancer(serviceId);
|
||||
}
|
||||
|
||||
protected static class RibbonServer implements ServiceInstance {
|
||||
public static class RibbonServer implements ServiceInstance {
|
||||
private final String serviceId;
|
||||
private final Server server;
|
||||
private final boolean secure;
|
||||
private Map<String, String> metadata;
|
||||
|
||||
protected RibbonServer(String serviceId, Server server) {
|
||||
public RibbonServer(String serviceId, Server server) {
|
||||
this(serviceId, server, false, Collections.<String, String> emptyMap());
|
||||
}
|
||||
|
||||
protected RibbonServer(String serviceId, Server server, boolean secure,
|
||||
public RibbonServer(String serviceId, Server server, boolean secure,
|
||||
Map<String, String> metadata) {
|
||||
this.serviceId = serviceId;
|
||||
this.server = server;
|
||||
|
||||
-33
@@ -1,33 +0,0 @@
|
||||
package org.springframework.cloud.netflix.ribbon;
|
||||
|
||||
import com.netflix.config.ConfigurationManager;
|
||||
import com.netflix.config.DynamicPropertyFactory;
|
||||
import com.netflix.config.DynamicStringProperty;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
public class RibbonProperyUtils {
|
||||
|
||||
public static final String VALUE_NOT_SET = "__not__set__";
|
||||
|
||||
public static final String DEFAULT_NAMESPACE = "ribbon";
|
||||
|
||||
public static void setRibbonProperty(String serviceId, String suffix, String value) {
|
||||
// how to set the namespace properly?
|
||||
String key = getRibbonKey(serviceId, suffix);
|
||||
DynamicStringProperty property = getProperty(key);
|
||||
if (property.get().equals(VALUE_NOT_SET)) {
|
||||
ConfigurationManager.getConfigInstance().setProperty(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
public static String getRibbonKey(String serviceId, String suffix) {
|
||||
return serviceId + "." + DEFAULT_NAMESPACE + "." + suffix;
|
||||
}
|
||||
|
||||
public static DynamicStringProperty getProperty(String key) {
|
||||
return DynamicPropertyFactory.getInstance().getStringProperty(key, VALUE_NOT_SET);
|
||||
}
|
||||
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
package org.springframework.cloud.netflix.ribbon;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import com.netflix.client.config.CommonClientConfigKey;
|
||||
import com.netflix.client.config.IClientConfig;
|
||||
import com.netflix.config.ConfigurationManager;
|
||||
import com.netflix.config.DynamicPropertyFactory;
|
||||
import com.netflix.config.DynamicStringProperty;
|
||||
import com.netflix.loadbalancer.Server;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
* @author Jacques-Etienne Beaudet
|
||||
*/
|
||||
public class RibbonUtils {
|
||||
|
||||
public static final String VALUE_NOT_SET = "__not__set__";
|
||||
|
||||
public static final String DEFAULT_NAMESPACE = "ribbon";
|
||||
|
||||
public static void setRibbonProperty(String serviceId, String suffix, String value) {
|
||||
// how to set the namespace properly?
|
||||
String key = getRibbonKey(serviceId, suffix);
|
||||
DynamicStringProperty property = getProperty(key);
|
||||
if (property.get().equals(VALUE_NOT_SET)) {
|
||||
ConfigurationManager.getConfigInstance().setProperty(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
public static String getRibbonKey(String serviceId, String suffix) {
|
||||
return serviceId + "." + DEFAULT_NAMESPACE + "." + suffix;
|
||||
}
|
||||
|
||||
public static DynamicStringProperty getProperty(String key) {
|
||||
return DynamicPropertyFactory.getInstance().getStringProperty(key, VALUE_NOT_SET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if client is secure. If the supplied {@link IClientConfig} has the {@link CommonClientConfigKey#IsSecure}
|
||||
* set, return that value. Otherwise, query the supplied {@link ServerIntrospector}.
|
||||
* @param config the supplied client configuration.
|
||||
* @param serverIntrospector
|
||||
* @param server
|
||||
* @return true if the client is secure
|
||||
*/
|
||||
public static boolean isSecure(IClientConfig config, ServerIntrospector serverIntrospector, Server server) {
|
||||
if (config != null) {
|
||||
Boolean isSecure = config.get(CommonClientConfigKey.IsSecure);
|
||||
if (isSecure != null) {
|
||||
return isSecure;
|
||||
}
|
||||
}
|
||||
|
||||
return serverIntrospector.isSecure(server);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the scheme to https if needed. If the uri doesn't start with https and
|
||||
* {@link #isSecure(IClientConfig, ServerIntrospector, Server)} is true, update the scheme.
|
||||
* This assumes the uri is already encoded to avoid double encoding.
|
||||
*
|
||||
* @param uri
|
||||
* @param config
|
||||
* @param serverIntrospector
|
||||
* @param server
|
||||
* @return
|
||||
*/
|
||||
public static URI updateToHttpsIfNeeded(URI uri, IClientConfig config, ServerIntrospector serverIntrospector,
|
||||
Server server) {
|
||||
String scheme = uri.getScheme();
|
||||
if (!"".equals(uri.toString()) && !"https".equals(scheme) && isSecure(config, serverIntrospector, server)) {
|
||||
UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUri(uri).scheme("https");
|
||||
if (uri.getRawQuery() != null) {
|
||||
// When building the URI, UriComponentsBuilder verify the allowed characters and does not
|
||||
// support the '+' so we replace it for its equivalent '%20'.
|
||||
// See issue https://jira.spring.io/browse/SPR-10172
|
||||
uriComponentsBuilder.replaceQuery(uri.getRawQuery().replace("+", "%20"));
|
||||
}
|
||||
return uriComponentsBuilder.build(true).toUri();
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.netflix.ribbon;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author Rico Pahlisch
|
||||
* @author Gregor Zurowski
|
||||
*/
|
||||
@ConfigurationProperties("ribbon")
|
||||
public class ServerIntrospectorProperties {
|
||||
|
||||
private List<Integer> securePorts = Arrays.asList(443,8443);
|
||||
|
||||
public List<Integer> getSecurePorts() {
|
||||
return securePorts;
|
||||
}
|
||||
|
||||
public void setSecurePorts(List<Integer> securePorts) {
|
||||
this.securePorts = securePorts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
ServerIntrospectorProperties that = (ServerIntrospectorProperties) o;
|
||||
return Objects.equals(securePorts, that.securePorts);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(securePorts);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new StringBuilder("ServerIntrospectorProperties{")
|
||||
.append("securePorts=").append(securePorts)
|
||||
.append("}").toString();
|
||||
}
|
||||
|
||||
}
|
||||
+33
-21
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.cloud.netflix.ribbon;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.cloud.context.named.NamedContextFactory;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
@@ -35,8 +37,10 @@ import com.netflix.loadbalancer.ILoadBalancer;
|
||||
*/
|
||||
public class SpringClientFactory extends NamedContextFactory<RibbonClientSpecification> {
|
||||
|
||||
static final String NAMESPACE = "ribbon";
|
||||
|
||||
public SpringClientFactory() {
|
||||
super(RibbonClientConfiguration.class, "ribbon", "ribbon.client.name");
|
||||
super(RibbonClientConfiguration.class, NAMESPACE, "ribbon.client.name");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,34 +75,37 @@ public class SpringClientFactory extends NamedContextFactory<RibbonClientSpecifi
|
||||
return getInstance(serviceId, RibbonLoadBalancerContext.class);
|
||||
}
|
||||
|
||||
private <C> C instantiateWithConfig(AnnotationConfigApplicationContext context,
|
||||
static <C> C instantiateWithConfig(Class<C> clazz, IClientConfig config) {
|
||||
return instantiateWithConfig(null, clazz, config);
|
||||
}
|
||||
|
||||
static <C> C instantiateWithConfig(AnnotationConfigApplicationContext context,
|
||||
Class<C> clazz, IClientConfig config) {
|
||||
C result = null;
|
||||
if (IClientConfigAware.class.isAssignableFrom(clazz)) {
|
||||
IClientConfigAware obj = (IClientConfigAware) BeanUtils.instantiate(clazz);
|
||||
obj.initWithNiwsConfig(config);
|
||||
@SuppressWarnings("unchecked")
|
||||
C value = (C) obj;
|
||||
result = value;
|
||||
|
||||
try {
|
||||
Constructor<C> constructor = clazz.getConstructor(IClientConfig.class);
|
||||
result = constructor.newInstance(config);
|
||||
} catch (Throwable e) {
|
||||
// Ignored
|
||||
}
|
||||
else {
|
||||
try {
|
||||
if (clazz.getConstructor(IClientConfig.class) != null) {
|
||||
result = clazz.getConstructor(IClientConfig.class)
|
||||
.newInstance(config);
|
||||
}
|
||||
else {
|
||||
result = BeanUtils.instantiate(clazz);
|
||||
}
|
||||
|
||||
if (result == null) {
|
||||
result = BeanUtils.instantiate(clazz);
|
||||
|
||||
if (result instanceof IClientConfigAware) {
|
||||
((IClientConfigAware) result).initWithNiwsConfig(config);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
// NOPMD
|
||||
|
||||
if (context != null) {
|
||||
context.getAutowireCapableBeanFactory().autowireBean(result);
|
||||
}
|
||||
}
|
||||
context.getAutowireCapableBeanFactory().autowireBean(result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <C> C getInstance(String name, Class<C> type) {
|
||||
C instance = super.getInstance(name, type);
|
||||
if (instance != null) {
|
||||
@@ -108,5 +115,10 @@ public class SpringClientFactory extends NamedContextFactory<RibbonClientSpecifi
|
||||
return instantiateWithConfig(getContext(name), type, config);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AnnotationConfigApplicationContext getContext(String name) {
|
||||
return super.getContext(name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
+29
-5
@@ -18,9 +18,7 @@ package org.springframework.cloud.netflix.ribbon;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import java.util.Objects;
|
||||
|
||||
import com.netflix.client.config.IClientConfig;
|
||||
import com.netflix.config.ConfigurationManager;
|
||||
@@ -34,8 +32,6 @@ import com.netflix.loadbalancer.ZoneAffinityServerListFilter;
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public class ZonePreferenceServerListFilter extends ZoneAffinityServerListFilter<Server> {
|
||||
|
||||
private String zone;
|
||||
@@ -66,4 +62,32 @@ public class ZonePreferenceServerListFilter extends ZoneAffinityServerListFilter
|
||||
return output;
|
||||
}
|
||||
|
||||
public String getZone() {
|
||||
return zone;
|
||||
}
|
||||
|
||||
public void setZone(String zone) {
|
||||
this.zone = zone;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
ZonePreferenceServerListFilter that = (ZonePreferenceServerListFilter) o;
|
||||
return Objects.equals(zone, that.zone);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(zone);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new StringBuilder("ZonePreferenceServerListFilter{")
|
||||
.append("zone='").append(zone).append("'")
|
||||
.append("}").toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.netflix.ribbon.apache;
|
||||
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.annotation.PreDestroy;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.config.RegistryBuilder;
|
||||
import org.apache.http.conn.HttpClientConnectionManager;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryListenerFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
|
||||
import org.springframework.cloud.commons.httpclient.ApacheHttpClientConnectionManagerFactory;
|
||||
import org.springframework.cloud.commons.httpclient.ApacheHttpClientFactory;
|
||||
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import com.netflix.client.AbstractLoadBalancerAwareClient;
|
||||
import com.netflix.client.RetryHandler;
|
||||
import com.netflix.client.config.CommonClientConfigKey;
|
||||
import com.netflix.client.config.DefaultClientConfigImpl;
|
||||
import com.netflix.client.config.IClientConfig;
|
||||
import com.netflix.loadbalancer.ILoadBalancer;
|
||||
import com.netflix.servo.monitor.Monitors;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnClass(name = "org.apache.http.client.HttpClient")
|
||||
@ConditionalOnProperty(name = "ribbon.httpclient.enabled", matchIfMissing = true)
|
||||
public class HttpClientRibbonConfiguration {
|
||||
@Value("${ribbon.client.name}")
|
||||
private String name = "client";
|
||||
|
||||
@Configuration
|
||||
protected static class ApacheHttpClientConfiguration {
|
||||
private final Timer connectionManagerTimer = new Timer(
|
||||
"RibbonApacheHttpClientConfiguration.connectionManagerTimer", true);
|
||||
private CloseableHttpClient httpClient;
|
||||
|
||||
@Autowired(required = false)
|
||||
private RegistryBuilder registryBuilder;
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(HttpClientConnectionManager.class)
|
||||
public HttpClientConnectionManager httpClientConnectionManager(
|
||||
IClientConfig config,
|
||||
ApacheHttpClientConnectionManagerFactory connectionManagerFactory) {
|
||||
Integer maxTotalConnections = config.getPropertyAsInteger(
|
||||
CommonClientConfigKey.MaxTotalConnections,
|
||||
DefaultClientConfigImpl.DEFAULT_MAX_TOTAL_CONNECTIONS);
|
||||
Integer maxConnectionsPerHost = config.getPropertyAsInteger(
|
||||
CommonClientConfigKey.MaxConnectionsPerHost,
|
||||
DefaultClientConfigImpl.DEFAULT_MAX_CONNECTIONS_PER_HOST);
|
||||
Integer timerRepeat = config.getPropertyAsInteger(
|
||||
CommonClientConfigKey.ConnectionCleanerRepeatInterval,
|
||||
DefaultClientConfigImpl.DEFAULT_CONNECTION_IDLE_TIMERTASK_REPEAT_IN_MSECS);
|
||||
Object timeToLiveObj = config
|
||||
.getProperty(CommonClientConfigKey.PoolKeepAliveTime);
|
||||
Long timeToLive = DefaultClientConfigImpl.DEFAULT_POOL_KEEP_ALIVE_TIME;
|
||||
Object ttlUnitObj = config
|
||||
.getProperty(CommonClientConfigKey.PoolKeepAliveTimeUnits);
|
||||
TimeUnit ttlUnit = DefaultClientConfigImpl.DEFAULT_POOL_KEEP_ALIVE_TIME_UNITS;
|
||||
if (timeToLiveObj instanceof Long) {
|
||||
timeToLive = (Long) timeToLiveObj;
|
||||
}
|
||||
if (ttlUnitObj instanceof TimeUnit) {
|
||||
ttlUnit = (TimeUnit) ttlUnitObj;
|
||||
}
|
||||
final HttpClientConnectionManager connectionManager = connectionManagerFactory
|
||||
.newConnectionManager(false, maxTotalConnections,
|
||||
maxConnectionsPerHost, timeToLive, ttlUnit, registryBuilder);
|
||||
this.connectionManagerTimer.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
connectionManager.closeExpiredConnections();
|
||||
}
|
||||
}, 30000, timerRepeat);
|
||||
return connectionManager;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(CloseableHttpClient.class)
|
||||
public CloseableHttpClient httpClient(ApacheHttpClientFactory httpClientFactory,
|
||||
HttpClientConnectionManager connectionManager, IClientConfig config) {
|
||||
Boolean followRedirects = config.getPropertyAsBoolean(
|
||||
CommonClientConfigKey.FollowRedirects,
|
||||
DefaultClientConfigImpl.DEFAULT_FOLLOW_REDIRECTS);
|
||||
Integer connectTimeout = config.getPropertyAsInteger(
|
||||
CommonClientConfigKey.ConnectTimeout,
|
||||
DefaultClientConfigImpl.DEFAULT_CONNECT_TIMEOUT);
|
||||
RequestConfig defaultRequestConfig = RequestConfig.custom()
|
||||
.setConnectTimeout(connectTimeout)
|
||||
.setRedirectsEnabled(followRedirects).build();
|
||||
this.httpClient = httpClientFactory.createBuilder().
|
||||
setDefaultRequestConfig(defaultRequestConfig).
|
||||
setConnectionManager(connectionManager).build();
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void destroy() throws Exception {
|
||||
connectionManagerTimer.cancel();
|
||||
if(httpClient != null) {
|
||||
httpClient.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(AbstractLoadBalancerAwareClient.class)
|
||||
@ConditionalOnMissingClass(value = "org.springframework.retry.support.RetryTemplate")
|
||||
public RibbonLoadBalancingHttpClient ribbonLoadBalancingHttpClient(
|
||||
IClientConfig config, ServerIntrospector serverIntrospector,
|
||||
ILoadBalancer loadBalancer, RetryHandler retryHandler, CloseableHttpClient httpClient) {
|
||||
RibbonLoadBalancingHttpClient client = new RibbonLoadBalancingHttpClient(httpClient, config, serverIntrospector);
|
||||
client.setLoadBalancer(loadBalancer);
|
||||
client.setRetryHandler(retryHandler);
|
||||
Monitors.registerObject("Client_" + this.name, client);
|
||||
return client;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(AbstractLoadBalancerAwareClient.class)
|
||||
@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
|
||||
public RetryableRibbonLoadBalancingHttpClient retryableRibbonLoadBalancingHttpClient(
|
||||
IClientConfig config, ServerIntrospector serverIntrospector,
|
||||
ILoadBalancer loadBalancer, RetryHandler retryHandler,
|
||||
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory, CloseableHttpClient httpClient,
|
||||
LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory,
|
||||
LoadBalancedRetryListenerFactory loadBalancedRetryListenerFactory) {
|
||||
RetryableRibbonLoadBalancingHttpClient client = new RetryableRibbonLoadBalancingHttpClient(
|
||||
httpClient, config, serverIntrospector, loadBalancedRetryPolicyFactory,
|
||||
loadBalancedBackOffPolicyFactory, loadBalancedRetryListenerFactory);
|
||||
client.setLoadBalancer(loadBalancer);
|
||||
client.setRetryHandler(retryHandler);
|
||||
Monitors.registerObject("Client_" + this.name, client);
|
||||
return client;
|
||||
}
|
||||
}
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cloud.netflix.ribbon.apache;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import org.apache.commons.lang.BooleanUtils;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpUriRequest;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryListenerFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.RetryableStatusCodeException;
|
||||
import org.springframework.cloud.client.loadbalancer.ServiceInstanceChooser;
|
||||
import org.springframework.cloud.netflix.feign.ribbon.FeignRetryPolicy;
|
||||
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient;
|
||||
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
|
||||
import org.springframework.http.HttpRequest;
|
||||
import org.springframework.retry.RetryCallback;
|
||||
import org.springframework.retry.RetryContext;
|
||||
import org.springframework.retry.RetryListener;
|
||||
import org.springframework.retry.backoff.BackOffPolicy;
|
||||
import org.springframework.retry.backoff.NoBackOffPolicy;
|
||||
import org.springframework.retry.policy.NeverRetryPolicy;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
import com.netflix.client.RequestSpecificRetryHandler;
|
||||
import com.netflix.client.RetryHandler;
|
||||
import com.netflix.client.config.CommonClientConfigKey;
|
||||
import com.netflix.client.config.IClientConfig;
|
||||
import com.netflix.loadbalancer.Server;
|
||||
|
||||
/**
|
||||
* An Apache HTTP client which leverages Spring Retry to retry failed requests.
|
||||
* @author Ryan Baxter
|
||||
* @author Gang Li
|
||||
*/
|
||||
public class RetryableRibbonLoadBalancingHttpClient extends RibbonLoadBalancingHttpClient
|
||||
implements ServiceInstanceChooser {
|
||||
private LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory = new LoadBalancedRetryPolicyFactory.NeverRetryFactory();
|
||||
private LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory =
|
||||
new LoadBalancedBackOffPolicyFactory.NoBackOffPolicyFactory();
|
||||
private LoadBalancedRetryListenerFactory loadBalancedRetryListenerFactory =
|
||||
new LoadBalancedRetryListenerFactory.DefaultRetryListenerFactory();
|
||||
|
||||
@Deprecated
|
||||
//TODO remove in 2.0.x
|
||||
public RetryableRibbonLoadBalancingHttpClient(IClientConfig config,
|
||||
ServerIntrospector serverIntrospector,
|
||||
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) {
|
||||
super(config, serverIntrospector);
|
||||
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
//TODO remove in 2.0.x
|
||||
public RetryableRibbonLoadBalancingHttpClient(CloseableHttpClient delegate,
|
||||
IClientConfig config, ServerIntrospector serverIntrospector,
|
||||
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) {
|
||||
super(delegate, config, serverIntrospector);
|
||||
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
//TODO remove in 2.0.x
|
||||
public RetryableRibbonLoadBalancingHttpClient(CloseableHttpClient delegate,
|
||||
IClientConfig config, ServerIntrospector serverIntrospector,
|
||||
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory,
|
||||
LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory) {
|
||||
super(delegate, config, serverIntrospector);
|
||||
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
|
||||
this.loadBalancedBackOffPolicyFactory = loadBalancedBackOffPolicyFactory;
|
||||
}
|
||||
|
||||
public RetryableRibbonLoadBalancingHttpClient(CloseableHttpClient delegate,
|
||||
IClientConfig config, ServerIntrospector serverIntrospector,
|
||||
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory,
|
||||
LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory,
|
||||
LoadBalancedRetryListenerFactory loadBalancedRetryListenerFactory) {
|
||||
super(delegate, config, serverIntrospector);
|
||||
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
|
||||
this.loadBalancedBackOffPolicyFactory = loadBalancedBackOffPolicyFactory;
|
||||
this.loadBalancedRetryListenerFactory = loadBalancedRetryListenerFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RibbonApacheHttpResponse execute(final RibbonApacheHttpRequest request, final IClientConfig configOverride) throws Exception {
|
||||
final RequestConfig.Builder builder = RequestConfig.custom();
|
||||
IClientConfig config = configOverride != null ? configOverride : this.config;
|
||||
builder.setConnectTimeout(config.get(
|
||||
CommonClientConfigKey.ConnectTimeout, this.connectTimeout));
|
||||
builder.setSocketTimeout(config.get(
|
||||
CommonClientConfigKey.ReadTimeout, this.readTimeout));
|
||||
builder.setRedirectsEnabled(config.get(
|
||||
CommonClientConfigKey.FollowRedirects, this.followRedirects));
|
||||
|
||||
final RequestConfig requestConfig = builder.build();
|
||||
final LoadBalancedRetryPolicy retryPolicy = loadBalancedRetryPolicyFactory.create(this.getClientName(), this);
|
||||
RetryCallback retryCallback = new RetryCallback() {
|
||||
@Override
|
||||
public RibbonApacheHttpResponse doWithRetry(RetryContext context) throws Exception {
|
||||
//on retries the policy will choose the server and set it in the context
|
||||
//extract the server and update the request being made
|
||||
RibbonApacheHttpRequest newRequest = request;
|
||||
if(context instanceof LoadBalancedRetryContext) {
|
||||
ServiceInstance service = ((LoadBalancedRetryContext)context).getServiceInstance();
|
||||
if(service != null) {
|
||||
//Reconstruct the request URI using the host and port set in the retry context
|
||||
newRequest = newRequest.withNewUri(new URI(service.getUri().getScheme(),
|
||||
newRequest.getURI().getUserInfo(), service.getHost(), service.getPort(),
|
||||
newRequest.getURI().getPath(), newRequest.getURI().getQuery(),
|
||||
newRequest.getURI().getFragment()));
|
||||
}
|
||||
}
|
||||
newRequest = getSecureRequest(request, configOverride);
|
||||
HttpUriRequest httpUriRequest = newRequest.toRequest(requestConfig);
|
||||
final HttpResponse httpResponse = RetryableRibbonLoadBalancingHttpClient.this.delegate.execute(httpUriRequest);
|
||||
if(retryPolicy.retryableStatusCode(httpResponse.getStatusLine().getStatusCode())) {
|
||||
if(CloseableHttpResponse.class.isInstance(httpResponse)) {
|
||||
((CloseableHttpResponse)httpResponse).close();
|
||||
}
|
||||
throw new RetryableStatusCodeException(RetryableRibbonLoadBalancingHttpClient.this.clientName,
|
||||
httpResponse.getStatusLine().getStatusCode());
|
||||
}
|
||||
return new RibbonApacheHttpResponse(httpResponse, httpUriRequest.getURI());
|
||||
}
|
||||
};
|
||||
return this.executeWithRetry(request, retryPolicy, retryCallback);
|
||||
}
|
||||
|
||||
private RibbonApacheHttpResponse executeWithRetry(RibbonApacheHttpRequest request, LoadBalancedRetryPolicy retryPolicy, RetryCallback<RibbonApacheHttpResponse, IOException> callback) throws Exception {
|
||||
RetryTemplate retryTemplate = new RetryTemplate();
|
||||
boolean retryable = request.getContext() == null ? true :
|
||||
BooleanUtils.toBooleanDefaultIfNull(request.getContext().getRetryable(), true);
|
||||
retryTemplate.setRetryPolicy(retryPolicy == null || !retryable ? new NeverRetryPolicy()
|
||||
: new RetryPolicy(request, retryPolicy, this, this.getClientName()));
|
||||
BackOffPolicy backOffPolicy = loadBalancedBackOffPolicyFactory.createBackOffPolicy(this.getClientName());
|
||||
retryTemplate.setBackOffPolicy(backOffPolicy == null ? new NoBackOffPolicy() : backOffPolicy);
|
||||
RetryListener[] retryListeners = this.loadBalancedRetryListenerFactory.createRetryListeners(this.getClientName());
|
||||
if (retryListeners != null && retryListeners.length != 0) {
|
||||
retryTemplate.setListeners(retryListeners);
|
||||
}
|
||||
return retryTemplate.execute(callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServiceInstance choose(String serviceId) {
|
||||
Server server = this.getLoadBalancer().chooseServer(serviceId);
|
||||
return new RibbonLoadBalancerClient.RibbonServer(serviceId, server);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestSpecificRetryHandler getRequestSpecificRetryHandler(RibbonApacheHttpRequest request, IClientConfig requestConfig) {
|
||||
return new RequestSpecificRetryHandler(false, false, RetryHandler.DEFAULT, null);
|
||||
}
|
||||
|
||||
static class RetryPolicy extends FeignRetryPolicy {
|
||||
public RetryPolicy(HttpRequest request, LoadBalancedRetryPolicy policy, ServiceInstanceChooser serviceInstanceChooser, String serviceName) {
|
||||
super(request, policy, serviceInstanceChooser, serviceName);
|
||||
}
|
||||
}
|
||||
}
|
||||
+26
-36
@@ -16,7 +16,8 @@
|
||||
|
||||
package org.springframework.cloud.netflix.ribbon.apache;
|
||||
|
||||
import java.io.InputStream;
|
||||
import static org.springframework.cloud.netflix.ribbon.support.RibbonRequestCustomizer.Runner.customize;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
|
||||
@@ -24,69 +25,58 @@ import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.client.methods.HttpUriRequest;
|
||||
import org.apache.http.client.methods.RequestBuilder;
|
||||
import org.apache.http.entity.BasicHttpEntity;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
import com.netflix.client.ClientRequest;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.springframework.cloud.netflix.ribbon.support.ContextAwareRequest;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandContext;
|
||||
|
||||
/**
|
||||
* @author Christian Lohmann
|
||||
*/
|
||||
@Getter
|
||||
public class RibbonApacheHttpRequest extends ClientRequest implements Cloneable {
|
||||
public class RibbonApacheHttpRequest extends ContextAwareRequest implements Cloneable {
|
||||
|
||||
private final String method;
|
||||
|
||||
private final MultiValueMap<String, String> headers;
|
||||
|
||||
private final MultiValueMap<String, String> params;
|
||||
|
||||
private final InputStream requestEntity;
|
||||
|
||||
public RibbonApacheHttpRequest(final String method, final URI uri,
|
||||
final Boolean retryable, final MultiValueMap<String, String> headers,
|
||||
final MultiValueMap<String, String> params, final InputStream requestEntity) {
|
||||
|
||||
this.method = method;
|
||||
this.uri = uri;
|
||||
this.isRetriable = retryable;
|
||||
this.headers = headers;
|
||||
this.params = params;
|
||||
this.requestEntity = requestEntity;
|
||||
public RibbonApacheHttpRequest(RibbonCommandContext context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public HttpUriRequest toRequest(final RequestConfig requestConfig) {
|
||||
final RequestBuilder builder = RequestBuilder.create(this.method);
|
||||
final RequestBuilder builder = RequestBuilder.create(this.context.getMethod());
|
||||
builder.setUri(this.uri);
|
||||
for (final String name : this.headers.keySet()) {
|
||||
final List<String> values = this.headers.get(name);
|
||||
for (final String name : this.context.getHeaders().keySet()) {
|
||||
final List<String> values = this.context.getHeaders().get(name);
|
||||
for (final String value : values) {
|
||||
builder.addHeader(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
for (final String name : this.params.keySet()) {
|
||||
final List<String> values = this.params.get(name);
|
||||
for (final String name : this.context.getParams().keySet()) {
|
||||
final List<String> values = this.context.getParams().get(name);
|
||||
for (final String value : values) {
|
||||
builder.addParameter(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.requestEntity != null) {
|
||||
if (this.context.getRequestEntity() != null) {
|
||||
final BasicHttpEntity entity;
|
||||
entity = new BasicHttpEntity();
|
||||
entity.setContent(this.requestEntity);
|
||||
entity.setContent(this.context.getRequestEntity());
|
||||
// if the entity contentLength isn't set, transfer-encoding will be set
|
||||
// to chunked in org.apache.http.protocol.RequestContent. See gh-1042
|
||||
Long contentLength = this.context.getContentLength();
|
||||
if ("GET".equals(this.context.getMethod()) && (contentLength == null || contentLength < 0)) {
|
||||
entity.setContentLength(0);
|
||||
} else if (contentLength != null) {
|
||||
entity.setContentLength(contentLength);
|
||||
}
|
||||
builder.setEntity(entity);
|
||||
}
|
||||
|
||||
customize(this.context.getRequestCustomizers(), builder);
|
||||
|
||||
builder.setConfig(requestConfig);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public RibbonApacheHttpRequest withNewUri(final URI uri) {
|
||||
return new RibbonApacheHttpRequest(this.method, uri, this.isRetriable,
|
||||
this.headers, this.params, this.requestEntity);
|
||||
public RibbonApacheHttpRequest withNewUri(URI uri) {
|
||||
return new RibbonApacheHttpRequest(newContext(uri));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+62
-90
@@ -16,121 +16,93 @@
|
||||
|
||||
package org.springframework.cloud.netflix.ribbon.apache;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.client.methods.HttpUriRequest;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import com.netflix.client.AbstractLoadBalancerAwareClient;
|
||||
import com.netflix.client.RequestSpecificRetryHandler;
|
||||
import com.netflix.client.RetryHandler;
|
||||
import com.netflix.client.config.CommonClientConfigKey;
|
||||
import com.netflix.client.config.DefaultClientConfigImpl;
|
||||
import com.netflix.client.config.IClientConfig;
|
||||
import com.netflix.loadbalancer.ILoadBalancer;
|
||||
import com.netflix.loadbalancer.Server;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.client.methods.HttpUriRequest;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
|
||||
import org.springframework.cloud.netflix.ribbon.support.AbstractLoadBalancingClient;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import static org.springframework.cloud.netflix.ribbon.RibbonUtils.updateToHttpsIfNeeded;
|
||||
|
||||
/**
|
||||
* @author Christian Lohmann
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
public class RibbonLoadBalancingHttpClient
|
||||
extends
|
||||
AbstractLoadBalancerAwareClient<RibbonApacheHttpRequest, RibbonApacheHttpResponse> {
|
||||
private final HttpClient delegate = HttpClientBuilder.create().build();
|
||||
// TODO: rename (ie new class that extends this in Dalston) to ApacheHttpLoadBalancingClient
|
||||
public class RibbonLoadBalancingHttpClient extends
|
||||
AbstractLoadBalancingClient<RibbonApacheHttpRequest, RibbonApacheHttpResponse, CloseableHttpClient> {
|
||||
|
||||
private int connectTimeout;
|
||||
|
||||
private int readTimeout;
|
||||
|
||||
private boolean secure;
|
||||
|
||||
private boolean followRedirects;
|
||||
|
||||
private boolean okToRetryOnAllOperations;
|
||||
|
||||
public RibbonLoadBalancingHttpClient() {
|
||||
super(null);
|
||||
this.setRetryHandler(RetryHandler.DEFAULT);
|
||||
public RibbonLoadBalancingHttpClient(IClientConfig config,
|
||||
ServerIntrospector serverIntrospector) {
|
||||
super(config, serverIntrospector);
|
||||
}
|
||||
|
||||
public RibbonLoadBalancingHttpClient(final ILoadBalancer lb) {
|
||||
super(lb);
|
||||
this.setRetryHandler(RetryHandler.DEFAULT);
|
||||
public RibbonLoadBalancingHttpClient(CloseableHttpClient delegate,
|
||||
IClientConfig config, ServerIntrospector serverIntrospector) {
|
||||
super(delegate, config, serverIntrospector);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initWithNiwsConfig(IClientConfig clientConfig) {
|
||||
super.initWithNiwsConfig(clientConfig);
|
||||
this.connectTimeout = clientConfig.getPropertyAsInteger(
|
||||
CommonClientConfigKey.ConnectTimeout,
|
||||
DefaultClientConfigImpl.DEFAULT_CONNECT_TIMEOUT);
|
||||
this.readTimeout = clientConfig.getPropertyAsInteger(
|
||||
CommonClientConfigKey.ReadTimeout,
|
||||
DefaultClientConfigImpl.DEFAULT_READ_TIMEOUT);
|
||||
this.secure = clientConfig.getPropertyAsBoolean(CommonClientConfigKey.IsSecure,
|
||||
false);
|
||||
this.followRedirects = clientConfig.getPropertyAsBoolean(
|
||||
CommonClientConfigKey.FollowRedirects,
|
||||
DefaultClientConfigImpl.DEFAULT_FOLLOW_REDIRECTS);
|
||||
this.okToRetryOnAllOperations = clientConfig.getPropertyAsBoolean(
|
||||
CommonClientConfigKey.OkToRetryOnAllOperations,
|
||||
DefaultClientConfigImpl.DEFAULT_OK_TO_RETRY_ON_ALL_OPERATIONS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestSpecificRetryHandler getRequestSpecificRetryHandler(
|
||||
final RibbonApacheHttpRequest request, final IClientConfig requestConfig) {
|
||||
if (this.okToRetryOnAllOperations) {
|
||||
return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(),
|
||||
requestConfig);
|
||||
}
|
||||
|
||||
if (!request.getMethod().equals("GET")) {
|
||||
return new RequestSpecificRetryHandler(true, false, this.getRetryHandler(),
|
||||
requestConfig);
|
||||
}
|
||||
else {
|
||||
return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(),
|
||||
requestConfig);
|
||||
}
|
||||
protected CloseableHttpClient createDelegate(IClientConfig config) {
|
||||
return HttpClientBuilder.create()
|
||||
// already defaults to 0 in builder, so resetting to 0 won't hurt
|
||||
.setMaxConnTotal(config.getPropertyAsInteger(
|
||||
CommonClientConfigKey.MaxTotalConnections, 0))
|
||||
// already defaults to 0 in builder, so resetting to 0 won't hurt
|
||||
.setMaxConnPerRoute(config.getPropertyAsInteger(
|
||||
CommonClientConfigKey.MaxConnectionsPerHost, 0))
|
||||
.disableCookieManagement().useSystemProperties() // for proxy
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RibbonApacheHttpResponse execute(RibbonApacheHttpRequest request,
|
||||
final IClientConfig configOverride) throws Exception {
|
||||
final RequestConfig.Builder builder = RequestConfig.custom();
|
||||
if (configOverride != null) {
|
||||
builder.setConnectTimeout(configOverride.get(
|
||||
CommonClientConfigKey.ConnectTimeout, this.connectTimeout));
|
||||
builder.setConnectionRequestTimeout(configOverride.get(
|
||||
CommonClientConfigKey.ReadTimeout, this.readTimeout));
|
||||
builder.setRedirectsEnabled(configOverride.get(
|
||||
CommonClientConfigKey.FollowRedirects, this.followRedirects));
|
||||
}
|
||||
else {
|
||||
builder.setConnectTimeout(this.connectTimeout);
|
||||
builder.setConnectionRequestTimeout(this.readTimeout);
|
||||
builder.setRedirectsEnabled(this.followRedirects);
|
||||
}
|
||||
IClientConfig config = configOverride != null ? configOverride : this.config;
|
||||
builder.setConnectTimeout(
|
||||
config.get(CommonClientConfigKey.ConnectTimeout, this.connectTimeout));
|
||||
builder.setSocketTimeout(
|
||||
config.get(CommonClientConfigKey.ReadTimeout, this.readTimeout));
|
||||
builder.setRedirectsEnabled(
|
||||
config.get(CommonClientConfigKey.FollowRedirects, this.followRedirects));
|
||||
|
||||
final RequestConfig requestConfig = builder.build();
|
||||
|
||||
if (isSecure(configOverride)) {
|
||||
final URI secureUri = UriComponentsBuilder.fromUri(request.getUri())
|
||||
.scheme("https").build().toUri();
|
||||
request = request.withNewUri(secureUri);
|
||||
}
|
||||
|
||||
request = getSecureRequest(request, configOverride);
|
||||
final HttpUriRequest httpUriRequest = request.toRequest(requestConfig);
|
||||
final HttpResponse httpResponse = this.delegate.execute(httpUriRequest);
|
||||
return new RibbonApacheHttpResponse(httpResponse, httpUriRequest.getURI());
|
||||
}
|
||||
|
||||
private boolean isSecure(final IClientConfig config) {
|
||||
return (config != null) ? config.get(CommonClientConfigKey.IsSecure)
|
||||
: this.secure;
|
||||
@Override
|
||||
public URI reconstructURIWithServer(Server server, URI original) {
|
||||
URI uri = updateToHttpsIfNeeded(original, this.config, this.serverIntrospector,
|
||||
server);
|
||||
return super.reconstructURIWithServer(server, uri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestSpecificRetryHandler getRequestSpecificRetryHandler(
|
||||
RibbonApacheHttpRequest request, IClientConfig requestConfig) {
|
||||
return new RequestSpecificRetryHandler(false, false, RetryHandler.DEFAULT,
|
||||
requestConfig);
|
||||
}
|
||||
|
||||
protected RibbonApacheHttpRequest getSecureRequest(RibbonApacheHttpRequest request, IClientConfig configOverride) {
|
||||
if (isSecure(configOverride)) {
|
||||
final URI secureUri = UriComponentsBuilder.fromUri(request.getUri())
|
||||
.scheme("https").build(true).toUri();
|
||||
return request.withNewUri(secureUri);
|
||||
}
|
||||
return request;
|
||||
}
|
||||
}
|
||||
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.netflix.ribbon.okhttp;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
|
||||
import org.springframework.cloud.netflix.ribbon.support.AbstractLoadBalancingClient;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import com.netflix.client.config.CommonClientConfigKey;
|
||||
import com.netflix.client.config.IClientConfig;
|
||||
import com.netflix.loadbalancer.ILoadBalancer;
|
||||
import com.netflix.loadbalancer.Server;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
|
||||
import static org.springframework.cloud.netflix.ribbon.RibbonUtils.updateToHttpsIfNeeded;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
public class OkHttpLoadBalancingClient
|
||||
extends AbstractLoadBalancingClient<OkHttpRibbonRequest, OkHttpRibbonResponse, OkHttpClient> {
|
||||
|
||||
public OkHttpLoadBalancingClient(IClientConfig config,
|
||||
ServerIntrospector serverIntrospector) {
|
||||
super(config, serverIntrospector);
|
||||
}
|
||||
|
||||
public OkHttpLoadBalancingClient(OkHttpClient delegate, IClientConfig config,
|
||||
ServerIntrospector serverIntrospector) {
|
||||
super(delegate, config, serverIntrospector);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected OkHttpClient createDelegate(IClientConfig config) {
|
||||
return new OkHttpClient();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OkHttpRibbonResponse execute(OkHttpRibbonRequest ribbonRequest,
|
||||
final IClientConfig configOverride) throws Exception {
|
||||
boolean secure = isSecure(configOverride);
|
||||
if (secure) {
|
||||
final URI secureUri = UriComponentsBuilder.fromUri(ribbonRequest.getUri())
|
||||
.scheme("https").build().toUri();
|
||||
ribbonRequest = ribbonRequest.withNewUri(secureUri);
|
||||
}
|
||||
|
||||
OkHttpClient httpClient = getOkHttpClient(configOverride, secure);
|
||||
final Request request = ribbonRequest.toRequest();
|
||||
Response response = httpClient.newCall(request).execute();
|
||||
return new OkHttpRibbonResponse(response, ribbonRequest.getUri());
|
||||
}
|
||||
|
||||
OkHttpClient getOkHttpClient(IClientConfig configOverride, boolean secure) {
|
||||
OkHttpClient.Builder builder = this.delegate.newBuilder();
|
||||
IClientConfig config = configOverride != null ? configOverride : this.config;
|
||||
builder.connectTimeout(config.get(
|
||||
CommonClientConfigKey.ConnectTimeout, this.connectTimeout), TimeUnit.MILLISECONDS);
|
||||
builder.readTimeout(config.get(
|
||||
CommonClientConfigKey.ReadTimeout, this.readTimeout), TimeUnit.MILLISECONDS);
|
||||
builder.followRedirects(config.get(
|
||||
CommonClientConfigKey.FollowRedirects, this.followRedirects));
|
||||
if (secure) {
|
||||
builder.followSslRedirects(configOverride.get(
|
||||
CommonClientConfigKey.FollowRedirects, this.followRedirects));
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI reconstructURIWithServer(Server server, URI original) {
|
||||
URI uri = updateToHttpsIfNeeded(original, this.config, this.serverIntrospector,
|
||||
server);
|
||||
return super.reconstructURIWithServer(server, uri);
|
||||
}
|
||||
}
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.netflix.ribbon.okhttp;
|
||||
|
||||
import okhttp3.ConnectionPool;
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.annotation.PreDestroy;
|
||||
import com.netflix.client.AbstractLoadBalancerAwareClient;
|
||||
import com.netflix.client.RetryHandler;
|
||||
import com.netflix.client.config.CommonClientConfigKey;
|
||||
import com.netflix.client.config.DefaultClientConfigImpl;
|
||||
import com.netflix.client.config.IClientConfig;
|
||||
import com.netflix.loadbalancer.ILoadBalancer;
|
||||
import com.netflix.servo.monitor.Monitors;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryListenerFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
|
||||
import org.springframework.cloud.commons.httpclient.OkHttpClientConnectionPoolFactory;
|
||||
import org.springframework.cloud.commons.httpclient.OkHttpClientFactory;
|
||||
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnProperty("ribbon.okhttp.enabled")
|
||||
@ConditionalOnClass(name = "okhttp3.OkHttpClient")
|
||||
public class OkHttpRibbonConfiguration {
|
||||
@Value("${ribbon.client.name}")
|
||||
private String name = "client";
|
||||
|
||||
@Configuration
|
||||
protected static class OkHttpClientConfiguration {
|
||||
private OkHttpClient httpClient;
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(ConnectionPool.class)
|
||||
public ConnectionPool httpClientConnectionPool(IClientConfig config, OkHttpClientConnectionPoolFactory connectionPoolFactory) {
|
||||
Integer maxTotalConnections = config.getPropertyAsInteger(
|
||||
CommonClientConfigKey.MaxTotalConnections,
|
||||
DefaultClientConfigImpl.DEFAULT_MAX_TOTAL_CONNECTIONS);
|
||||
Object timeToLiveObj = config
|
||||
.getProperty(CommonClientConfigKey.PoolKeepAliveTime);
|
||||
Long timeToLive = DefaultClientConfigImpl.DEFAULT_POOL_KEEP_ALIVE_TIME;
|
||||
Object ttlUnitObj = config
|
||||
.getProperty(CommonClientConfigKey.PoolKeepAliveTimeUnits);
|
||||
TimeUnit ttlUnit = DefaultClientConfigImpl.DEFAULT_POOL_KEEP_ALIVE_TIME_UNITS;
|
||||
if (timeToLiveObj instanceof Long) {
|
||||
timeToLive = (Long) timeToLiveObj;
|
||||
}
|
||||
if (ttlUnitObj instanceof TimeUnit) {
|
||||
ttlUnit = (TimeUnit) ttlUnitObj;
|
||||
}
|
||||
return connectionPoolFactory.create(maxTotalConnections, timeToLive, ttlUnit);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(OkHttpClient.class)
|
||||
public OkHttpClient client(OkHttpClientFactory httpClientFactory, ConnectionPool connectionPool, IClientConfig config) {
|
||||
Boolean followRedirects = config.getPropertyAsBoolean(
|
||||
CommonClientConfigKey.FollowRedirects,
|
||||
DefaultClientConfigImpl.DEFAULT_FOLLOW_REDIRECTS);
|
||||
Integer connectTimeout = config.getPropertyAsInteger(
|
||||
CommonClientConfigKey.ConnectTimeout,
|
||||
DefaultClientConfigImpl.DEFAULT_CONNECT_TIMEOUT);
|
||||
Integer readTimeout = config.getPropertyAsInteger(CommonClientConfigKey.ReadTimeout,
|
||||
DefaultClientConfigImpl.DEFAULT_READ_TIMEOUT);
|
||||
this.httpClient = httpClientFactory.createBuilder(false).
|
||||
connectTimeout(connectTimeout, TimeUnit.MILLISECONDS).
|
||||
readTimeout(readTimeout, TimeUnit.MILLISECONDS).
|
||||
followRedirects(followRedirects).
|
||||
connectionPool(connectionPool).build();
|
||||
return this.httpClient;
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void destroy() {
|
||||
if(httpClient != null) {
|
||||
httpClient.dispatcher().executorService().shutdown();
|
||||
httpClient.connectionPool().evictAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(AbstractLoadBalancerAwareClient.class)
|
||||
@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
|
||||
public RetryableOkHttpLoadBalancingClient okHttpLoadBalancingClient(
|
||||
IClientConfig config,
|
||||
ServerIntrospector serverIntrospector,
|
||||
ILoadBalancer loadBalancer,
|
||||
RetryHandler retryHandler,
|
||||
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory,
|
||||
OkHttpClient delegate,
|
||||
LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory,
|
||||
LoadBalancedRetryListenerFactory loadBalancedRetryListenerFactory) {
|
||||
RetryableOkHttpLoadBalancingClient client = new RetryableOkHttpLoadBalancingClient(delegate, config,
|
||||
serverIntrospector, loadBalancedRetryPolicyFactory, loadBalancedBackOffPolicyFactory, loadBalancedRetryListenerFactory);
|
||||
client.setLoadBalancer(loadBalancer);
|
||||
client.setRetryHandler(retryHandler);
|
||||
Monitors.registerObject("Client_" + this.name, client);
|
||||
return client;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(AbstractLoadBalancerAwareClient.class)
|
||||
@ConditionalOnMissingClass(value = "org.springframework.retry.support.RetryTemplate")
|
||||
public OkHttpLoadBalancingClient retryableOkHttpLoadBalancingClient(
|
||||
IClientConfig config,
|
||||
ServerIntrospector serverIntrospector, ILoadBalancer loadBalancer,
|
||||
RetryHandler retryHandler, OkHttpClient delegate) {
|
||||
OkHttpLoadBalancingClient client = new OkHttpLoadBalancingClient(delegate, config,
|
||||
serverIntrospector);
|
||||
client.setLoadBalancer(loadBalancer);
|
||||
client.setRetryHandler(retryHandler);
|
||||
Monitors.registerObject("Client_" + this.name, client);
|
||||
return client;
|
||||
}
|
||||
}
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.netflix.ribbon.okhttp;
|
||||
|
||||
import static org.springframework.cloud.netflix.ribbon.support.RibbonRequestCustomizer.Runner.customize;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.cloud.netflix.ribbon.support.ContextAwareRequest;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandContext;
|
||||
|
||||
import okhttp3.Headers;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.internal.http.HttpMethod;
|
||||
import okio.BufferedSink;
|
||||
import okio.Okio;
|
||||
import okio.Source;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
public class OkHttpRibbonRequest extends ContextAwareRequest implements Cloneable {
|
||||
|
||||
public OkHttpRibbonRequest(RibbonCommandContext context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public Request toRequest() {
|
||||
Headers.Builder headers = new Headers.Builder();
|
||||
for (String name : this.context.getHeaders().keySet()) {
|
||||
List<String> values = this.context.getHeaders().get(name);
|
||||
for (String value : values) {
|
||||
headers.add(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
HttpUrl.Builder url = HttpUrl.get(this.uri).newBuilder();
|
||||
for (String name : this.context.getParams().keySet()) {
|
||||
List<String> values = this.context.getParams().get(name);
|
||||
for (String value : values) {
|
||||
url.addQueryParameter(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
RequestBody requestBody = null;
|
||||
|
||||
if (this.context.getRequestEntity() != null && HttpMethod.permitsRequestBody(this.context.getMethod())) {
|
||||
MediaType mediaType = null;
|
||||
if (headers.get("Content-Type") != null) {
|
||||
mediaType = MediaType.parse(headers.get("Content-Type"));
|
||||
}
|
||||
requestBody = new InputStreamRequestBody(this.context.getRequestEntity(), mediaType, this.context.getContentLength());
|
||||
}
|
||||
|
||||
Request.Builder builder = new Request.Builder()
|
||||
.url(url.build())
|
||||
.headers(headers.build())
|
||||
.method(this.context.getMethod(), requestBody);
|
||||
|
||||
customize(this.context.getRequestCustomizers(), builder);
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public OkHttpRibbonRequest withNewUri(final URI uri) {
|
||||
return new OkHttpRibbonRequest(newContext(uri));
|
||||
}
|
||||
|
||||
static class InputStreamRequestBody extends RequestBody {
|
||||
|
||||
private InputStream inputStream;
|
||||
private MediaType mediaType;
|
||||
private Long contentLength;
|
||||
|
||||
InputStreamRequestBody(InputStream inputStream, MediaType mediaType, Long contentLength) {
|
||||
this.inputStream = inputStream;
|
||||
this.mediaType = mediaType;
|
||||
this.contentLength = contentLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaType contentType() {
|
||||
return mediaType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long contentLength() {
|
||||
if (contentLength != null) {
|
||||
return contentLength;
|
||||
}
|
||||
try {
|
||||
return inputStream.available();
|
||||
} catch (IOException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(BufferedSink sink) throws IOException {
|
||||
Source source = null;
|
||||
try {
|
||||
source = Okio.source(inputStream);
|
||||
sink.writeAll(source);
|
||||
} finally {
|
||||
if (source != null) {
|
||||
source.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.netflix.ribbon.okhttp;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Type;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.google.common.reflect.TypeToken;
|
||||
import com.netflix.client.ClientException;
|
||||
import com.netflix.client.http.CaseInsensitiveMultiMap;
|
||||
import com.netflix.client.http.HttpHeaders;
|
||||
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
public class OkHttpRibbonResponse implements com.netflix.client.http.HttpResponse {
|
||||
|
||||
private final ResponseBody body;
|
||||
private URI uri;
|
||||
private Response response;
|
||||
|
||||
public OkHttpRibbonResponse(Response response, URI uri) {
|
||||
Assert.notNull(response, "response can not be null");
|
||||
this.response = response;
|
||||
this.body = response.body();
|
||||
this.uri = uri;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getStatus() {
|
||||
return this.response.code();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStatusLine() {
|
||||
return this.response.message();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getPayload() throws ClientException {
|
||||
if (!hasPayload()) {
|
||||
return null;
|
||||
}
|
||||
return this.body.byteStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPayload() {
|
||||
return this.body != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSuccess() {
|
||||
return this.response.isSuccessful();
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getRequestedURI() {
|
||||
return this.uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Collection<String>> getHeaders() {
|
||||
final Map<String, Collection<String>> headers = new HashMap<>();
|
||||
for (Map.Entry<String,List<String>> entry : this.response.headers().toMultimap().entrySet()) {
|
||||
String name = entry.getKey();
|
||||
for (String value : entry.getValue()) {
|
||||
if (headers.containsKey(name)) {
|
||||
headers.get(name).add(value);
|
||||
} else {
|
||||
final List<String> values = new ArrayList<>();
|
||||
values.add(value);
|
||||
headers.put(name, values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders getHttpHeaders() {
|
||||
final CaseInsensitiveMultiMap headers = new CaseInsensitiveMultiMap();
|
||||
for (Map.Entry<String,List<String>> entry : this.response.headers().toMultimap().entrySet()) {
|
||||
for (String value : entry.getValue()) {
|
||||
headers.addHeader(entry.getKey(), value);
|
||||
}
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
this.response.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() {
|
||||
if (this.body == null) {
|
||||
return null;
|
||||
}
|
||||
return this.body.byteStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasEntity() {
|
||||
return hasPayload();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getEntity(Class<T> type) throws Exception {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getEntity(Type type) throws Exception {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getEntity(TypeToken<T> type) throws Exception {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cloud.netflix.ribbon.okhttp;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
|
||||
import java.net.URI;
|
||||
import org.apache.commons.lang.BooleanUtils;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedBackOffPolicyFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryListenerFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.RetryableStatusCodeException;
|
||||
import org.springframework.cloud.client.loadbalancer.ServiceInstanceChooser;
|
||||
import org.springframework.cloud.netflix.feign.ribbon.FeignRetryPolicy;
|
||||
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient;
|
||||
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
|
||||
import org.springframework.http.HttpRequest;
|
||||
import org.springframework.retry.RetryCallback;
|
||||
import org.springframework.retry.RetryContext;
|
||||
import org.springframework.retry.RetryListener;
|
||||
import org.springframework.retry.backoff.BackOffPolicy;
|
||||
import org.springframework.retry.backoff.NoBackOffPolicy;
|
||||
import org.springframework.retry.policy.NeverRetryPolicy;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import com.netflix.client.RequestSpecificRetryHandler;
|
||||
import com.netflix.client.RetryHandler;
|
||||
import com.netflix.client.config.IClientConfig;
|
||||
import com.netflix.loadbalancer.Server;
|
||||
|
||||
/**
|
||||
* An OK HTTP client which leverages Spring Retry to retry failed request.
|
||||
* @author Ryan Baxter
|
||||
* @author Gang Li
|
||||
*/
|
||||
public class RetryableOkHttpLoadBalancingClient extends OkHttpLoadBalancingClient implements ServiceInstanceChooser {
|
||||
|
||||
private LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory;
|
||||
private LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory =
|
||||
new LoadBalancedBackOffPolicyFactory.NoBackOffPolicyFactory();
|
||||
private LoadBalancedRetryListenerFactory loadBalancedRetryListenerFactory =
|
||||
new LoadBalancedRetryListenerFactory.DefaultRetryListenerFactory();
|
||||
|
||||
@Deprecated
|
||||
//TODO remove in 2.0.x
|
||||
public RetryableOkHttpLoadBalancingClient(OkHttpClient delegate, IClientConfig config, ServerIntrospector serverIntrospector,
|
||||
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) {
|
||||
super(delegate, config, serverIntrospector);
|
||||
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
//TODO remove in 2.0.x
|
||||
public RetryableOkHttpLoadBalancingClient(OkHttpClient delegate, IClientConfig config, ServerIntrospector serverIntrospector,
|
||||
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory,
|
||||
LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory) {
|
||||
super(delegate, config, serverIntrospector);
|
||||
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
|
||||
this.loadBalancedBackOffPolicyFactory = loadBalancedBackOffPolicyFactory;
|
||||
}
|
||||
|
||||
public RetryableOkHttpLoadBalancingClient(OkHttpClient delegate, IClientConfig config, ServerIntrospector serverIntrospector,
|
||||
LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory,
|
||||
LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory,
|
||||
LoadBalancedRetryListenerFactory loadBalancedRetryListenerFactory) {
|
||||
super(delegate, config, serverIntrospector);
|
||||
this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory;
|
||||
this.loadBalancedBackOffPolicyFactory = loadBalancedBackOffPolicyFactory;
|
||||
this.loadBalancedRetryListenerFactory = loadBalancedRetryListenerFactory;
|
||||
}
|
||||
|
||||
private OkHttpRibbonResponse executeWithRetry(OkHttpRibbonRequest request, LoadBalancedRetryPolicy retryPolicy,
|
||||
RetryCallback<OkHttpRibbonResponse, Exception> callback) throws Exception {
|
||||
RetryTemplate retryTemplate = new RetryTemplate();
|
||||
BackOffPolicy backOffPolicy = loadBalancedBackOffPolicyFactory.createBackOffPolicy(this.getClientName());
|
||||
retryTemplate.setBackOffPolicy(backOffPolicy == null ? new NoBackOffPolicy() : backOffPolicy);
|
||||
RetryListener[] retryListeners = this.loadBalancedRetryListenerFactory.createRetryListeners(this.getClientName());
|
||||
if (retryListeners != null && retryListeners.length != 0) {
|
||||
retryTemplate.setListeners(retryListeners);
|
||||
}
|
||||
boolean retryable = request.getContext() == null ? true :
|
||||
BooleanUtils.toBooleanDefaultIfNull(request.getContext().getRetryable(), true);
|
||||
retryTemplate.setRetryPolicy(retryPolicy == null || !retryable ? new NeverRetryPolicy()
|
||||
: new RetryPolicy(request, retryPolicy, this, this.getClientName()));
|
||||
return retryTemplate.execute(callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OkHttpRibbonResponse execute(final OkHttpRibbonRequest ribbonRequest,
|
||||
final IClientConfig configOverride) throws Exception {
|
||||
final LoadBalancedRetryPolicy retryPolicy = loadBalancedRetryPolicyFactory.create(this.getClientName(), this);
|
||||
RetryCallback<OkHttpRibbonResponse, Exception> retryCallback = new RetryCallback<OkHttpRibbonResponse, Exception>() {
|
||||
@Override
|
||||
public OkHttpRibbonResponse doWithRetry(RetryContext context) throws Exception {
|
||||
//on retries the policy will choose the server and set it in the context
|
||||
//extract the server and update the request being made
|
||||
OkHttpRibbonRequest newRequest = ribbonRequest;
|
||||
if(context instanceof LoadBalancedRetryContext) {
|
||||
ServiceInstance service = ((LoadBalancedRetryContext)context).getServiceInstance();
|
||||
if(service != null) {
|
||||
//Reconstruct the request URI using the host and port set in the retry context
|
||||
newRequest = newRequest.withNewUri(new URI(service.getUri().getScheme(),
|
||||
newRequest.getURI().getUserInfo(), service.getHost(), service.getPort(),
|
||||
newRequest.getURI().getPath(), newRequest.getURI().getQuery(),
|
||||
newRequest.getURI().getFragment()));
|
||||
}
|
||||
}
|
||||
if (isSecure(configOverride)) {
|
||||
final URI secureUri = UriComponentsBuilder.fromUri(newRequest.getUri())
|
||||
.scheme("https").build().toUri();
|
||||
newRequest = newRequest.withNewUri(secureUri);
|
||||
}
|
||||
OkHttpClient httpClient = getOkHttpClient(configOverride, secure);
|
||||
|
||||
final Request request = newRequest.toRequest();
|
||||
Response response = httpClient.newCall(request).execute();
|
||||
if(retryPolicy.retryableStatusCode(response.code())) {
|
||||
response.close();
|
||||
throw new RetryableStatusCodeException(RetryableOkHttpLoadBalancingClient.this.clientName, response.code());
|
||||
}
|
||||
return new OkHttpRibbonResponse(response, newRequest.getUri());
|
||||
}
|
||||
};
|
||||
return this.executeWithRetry(ribbonRequest, retryPolicy, retryCallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServiceInstance choose(String serviceId) {
|
||||
Server server = this.getLoadBalancer().chooseServer(serviceId);
|
||||
return new RibbonLoadBalancerClient.RibbonServer(serviceId,
|
||||
server);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestSpecificRetryHandler getRequestSpecificRetryHandler(OkHttpRibbonRequest request, IClientConfig requestConfig) {
|
||||
return new RequestSpecificRetryHandler(false, false, RetryHandler.DEFAULT, null);
|
||||
}
|
||||
|
||||
static class RetryPolicy extends FeignRetryPolicy {
|
||||
public RetryPolicy(HttpRequest request, LoadBalancedRetryPolicy policy, ServiceInstanceChooser serviceInstanceChooser, String serviceName) {
|
||||
super(request, policy, serviceInstanceChooser, serviceName);
|
||||
}
|
||||
}
|
||||
}
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright 2013-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.netflix.ribbon.support;
|
||||
|
||||
import com.netflix.loadbalancer.reactive.LoadBalancerCommand;
|
||||
import org.springframework.cloud.netflix.ribbon.DefaultServerIntrospector;
|
||||
import org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration;
|
||||
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
|
||||
|
||||
import com.netflix.client.AbstractLoadBalancerAwareClient;
|
||||
import com.netflix.client.IResponse;
|
||||
import com.netflix.client.RequestSpecificRetryHandler;
|
||||
import com.netflix.client.RetryHandler;
|
||||
import com.netflix.client.config.CommonClientConfigKey;
|
||||
import com.netflix.client.config.DefaultClientConfigImpl;
|
||||
import com.netflix.client.config.IClientConfig;
|
||||
import com.netflix.loadbalancer.ILoadBalancer;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
public abstract class AbstractLoadBalancingClient<S extends ContextAwareRequest, T extends IResponse, D> extends
|
||||
AbstractLoadBalancerAwareClient<S, T> {
|
||||
|
||||
protected int connectTimeout;
|
||||
|
||||
protected int readTimeout;
|
||||
|
||||
protected boolean secure;
|
||||
|
||||
protected boolean followRedirects;
|
||||
|
||||
protected boolean okToRetryOnAllOperations;
|
||||
|
||||
protected final D delegate;
|
||||
protected final IClientConfig config;
|
||||
protected final ServerIntrospector serverIntrospector;
|
||||
|
||||
@Deprecated
|
||||
public AbstractLoadBalancingClient() {
|
||||
super(null);
|
||||
this.config = new DefaultClientConfigImpl();
|
||||
this.delegate = createDelegate(this.config);
|
||||
this.serverIntrospector = new DefaultServerIntrospector();
|
||||
this.setRetryHandler(RetryHandler.DEFAULT);
|
||||
initWithNiwsConfig(config);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public AbstractLoadBalancingClient(final ILoadBalancer lb) {
|
||||
super(lb);
|
||||
this.config = new DefaultClientConfigImpl();
|
||||
this.delegate = createDelegate(config);
|
||||
this.serverIntrospector = new DefaultServerIntrospector();
|
||||
this.setRetryHandler(RetryHandler.DEFAULT);
|
||||
initWithNiwsConfig(config);
|
||||
}
|
||||
|
||||
protected AbstractLoadBalancingClient(IClientConfig config, ServerIntrospector serverIntrospector) {
|
||||
super(null);
|
||||
this.delegate = createDelegate(config);
|
||||
this.config = config;
|
||||
this.serverIntrospector = serverIntrospector;
|
||||
this.setRetryHandler(RetryHandler.DEFAULT);
|
||||
initWithNiwsConfig(config);
|
||||
}
|
||||
|
||||
protected AbstractLoadBalancingClient(D delegate, IClientConfig config, ServerIntrospector serverIntrospector) {
|
||||
super(null);
|
||||
this.delegate = delegate;
|
||||
this.config = config;
|
||||
this.serverIntrospector = serverIntrospector;
|
||||
this.setRetryHandler(RetryHandler.DEFAULT);
|
||||
initWithNiwsConfig(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initWithNiwsConfig(IClientConfig clientConfig) {
|
||||
super.initWithNiwsConfig(clientConfig);
|
||||
this.connectTimeout = clientConfig.getPropertyAsInteger(
|
||||
CommonClientConfigKey.ConnectTimeout,
|
||||
RibbonClientConfiguration.DEFAULT_CONNECT_TIMEOUT);
|
||||
this.readTimeout = clientConfig.getPropertyAsInteger(
|
||||
CommonClientConfigKey.ReadTimeout,
|
||||
RibbonClientConfiguration.DEFAULT_READ_TIMEOUT);
|
||||
this.secure = clientConfig.getPropertyAsBoolean(CommonClientConfigKey.IsSecure,
|
||||
false);
|
||||
this.followRedirects = clientConfig.getPropertyAsBoolean(
|
||||
CommonClientConfigKey.FollowRedirects,
|
||||
DefaultClientConfigImpl.DEFAULT_FOLLOW_REDIRECTS);
|
||||
this.okToRetryOnAllOperations = clientConfig.getPropertyAsBoolean(
|
||||
CommonClientConfigKey.OkToRetryOnAllOperations,
|
||||
DefaultClientConfigImpl.DEFAULT_OK_TO_RETRY_ON_ALL_OPERATIONS);
|
||||
}
|
||||
|
||||
protected abstract D createDelegate(IClientConfig config);
|
||||
|
||||
public D getDelegate() {
|
||||
return this.delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestSpecificRetryHandler getRequestSpecificRetryHandler(
|
||||
final S request, final IClientConfig requestConfig) {
|
||||
if (this.okToRetryOnAllOperations) {
|
||||
return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(),
|
||||
requestConfig);
|
||||
}
|
||||
|
||||
if (!request.getContext().getMethod().equals("GET")) {
|
||||
return new RequestSpecificRetryHandler(true, false, this.getRetryHandler(),
|
||||
requestConfig);
|
||||
}
|
||||
else {
|
||||
return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(),
|
||||
requestConfig);
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isSecure(final IClientConfig config) {
|
||||
if(config != null) {
|
||||
Boolean result = config.get(CommonClientConfigKey.IsSecure);
|
||||
if(result != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return this.secure;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void customizeLoadBalancerCommandBuilder(S request, IClientConfig config, LoadBalancerCommand.Builder<T> builder) {
|
||||
if (request.getLoadBalancerKey() != null) {
|
||||
builder.withServerLocator(request.getLoadBalancerKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2013-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.netflix.ribbon.support;
|
||||
|
||||
import com.netflix.client.ClientRequest;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpRequest;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandContext;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
public abstract class ContextAwareRequest extends ClientRequest implements HttpRequest {
|
||||
protected final RibbonCommandContext context;
|
||||
private HttpHeaders httpHeaders;
|
||||
|
||||
public ContextAwareRequest(RibbonCommandContext context) {
|
||||
this.context = context;
|
||||
MultiValueMap<String, String> headers = context.getHeaders();
|
||||
this.httpHeaders = new HttpHeaders();
|
||||
for(String key : headers.keySet()) {
|
||||
this.httpHeaders.put(key, headers.get(key));
|
||||
}
|
||||
this.uri = context.uri();
|
||||
this.isRetriable = context.getRetryable();
|
||||
this.loadBalancerKey = context.getLoadBalancerKey();
|
||||
}
|
||||
|
||||
public RibbonCommandContext getContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpMethod getMethod() {
|
||||
return HttpMethod.valueOf(context.getMethod());
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getURI() {
|
||||
return this.getUri();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders getHeaders() {
|
||||
return httpHeaders;
|
||||
}
|
||||
|
||||
protected RibbonCommandContext newContext(URI uri) {
|
||||
RibbonCommandContext commandContext = new RibbonCommandContext(this.context.getServiceId(),
|
||||
this.context.getMethod(), uri.toString(), this.context.getRetryable(),
|
||||
this.context.getHeaders(), this.context.getParams(), this.context.getRequestEntity(),
|
||||
this.context.getRequestCustomizers(), this.context.getContentLength(),
|
||||
this.context.getLoadBalancerKey());
|
||||
return commandContext;
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2013-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.netflix.ribbon.support;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
public interface RibbonRequestCustomizer<B> {
|
||||
boolean accepts(Class builderClass);
|
||||
void customize(B builder);
|
||||
|
||||
class Runner {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static void customize(List<RibbonRequestCustomizer> customizers, Object builder) {
|
||||
for (RibbonRequestCustomizer customizer : customizers) {
|
||||
if (customizer.accepts(builder.getClass())) {
|
||||
customizer.customize(builder);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+28
-28
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2016 the original author or authors.
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -30,39 +30,39 @@ import rx.Subscription;
|
||||
*/
|
||||
class DeferredResultSubscriber<T> extends Subscriber<T> implements Runnable {
|
||||
|
||||
private final DeferredResult<T> deferredResult;
|
||||
private final DeferredResult<T> deferredResult;
|
||||
|
||||
private final Subscription subscription;
|
||||
private final Subscription subscription;
|
||||
|
||||
private boolean completed;
|
||||
private boolean completed;
|
||||
|
||||
public DeferredResultSubscriber(Observable<T> observable, DeferredResult<T> deferredResult) {
|
||||
public DeferredResultSubscriber(Observable<T> observable, DeferredResult<T> deferredResult) {
|
||||
|
||||
this.deferredResult = deferredResult;
|
||||
this.deferredResult.onTimeout(this);
|
||||
this.deferredResult.onCompletion(this);
|
||||
this.subscription = observable.subscribe(this);
|
||||
}
|
||||
this.deferredResult = deferredResult;
|
||||
this.deferredResult.onTimeout(this);
|
||||
this.deferredResult.onCompletion(this);
|
||||
this.subscription = observable.subscribe(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNext(T value) {
|
||||
if (!completed) {
|
||||
deferredResult.setResult(value);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void onNext(T value) {
|
||||
if (!completed) {
|
||||
deferredResult.setResult(value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable e) {
|
||||
deferredResult.setErrorResult(e);
|
||||
}
|
||||
@Override
|
||||
public void onError(Throwable e) {
|
||||
deferredResult.setErrorResult(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCompleted() {
|
||||
completed = true;
|
||||
}
|
||||
@Override
|
||||
public void onCompleted() {
|
||||
completed = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
@Override
|
||||
public void run() {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
+45
-45
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2016 the original author or authors.
|
||||
* Copyright 2013-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -33,58 +33,58 @@ import rx.Subscription;
|
||||
*/
|
||||
class ResponseBodyEmitterSubscriber<T> extends Subscriber<T> implements Runnable {
|
||||
|
||||
private final MediaType mediaType;
|
||||
private final MediaType mediaType;
|
||||
|
||||
private final Subscription subscription;
|
||||
private final Subscription subscription;
|
||||
|
||||
private final ResponseBodyEmitter responseBodyEmitter;
|
||||
private final ResponseBodyEmitter responseBodyEmitter;
|
||||
|
||||
private boolean completed;
|
||||
private boolean completed;
|
||||
|
||||
/**
|
||||
* Creates new instance of {@link ResponseBodyEmitterSubscriber} with response media type, observable and response
|
||||
* emitter.
|
||||
*
|
||||
* @param mediaType the marshaled object media type
|
||||
* @param observable the observable
|
||||
* @param responseBodyEmitter the response emitter
|
||||
*/
|
||||
public ResponseBodyEmitterSubscriber(MediaType mediaType, Observable<T> observable, ResponseBodyEmitter responseBodyEmitter) {
|
||||
/**
|
||||
* Creates new instance of {@link ResponseBodyEmitterSubscriber} with response media type, observable and response
|
||||
* emitter.
|
||||
*
|
||||
* @param mediaType the marshaled object media type
|
||||
* @param observable the observable
|
||||
* @param responseBodyEmitter the response emitter
|
||||
*/
|
||||
public ResponseBodyEmitterSubscriber(MediaType mediaType, Observable<T> observable, ResponseBodyEmitter responseBodyEmitter) {
|
||||
|
||||
this.mediaType = mediaType;
|
||||
this.responseBodyEmitter = responseBodyEmitter;
|
||||
this.responseBodyEmitter.onTimeout(this);
|
||||
this.responseBodyEmitter.onCompletion(this);
|
||||
this.subscription = observable.subscribe(this);
|
||||
}
|
||||
this.mediaType = mediaType;
|
||||
this.responseBodyEmitter = responseBodyEmitter;
|
||||
this.responseBodyEmitter.onTimeout(this);
|
||||
this.responseBodyEmitter.onCompletion(this);
|
||||
this.subscription = observable.subscribe(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNext(T value) {
|
||||
@Override
|
||||
public void onNext(T value) {
|
||||
|
||||
try {
|
||||
if(!completed) {
|
||||
responseBodyEmitter.send(value, mediaType);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
try {
|
||||
if(!completed) {
|
||||
responseBodyEmitter.send(value, mediaType);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable e) {
|
||||
responseBodyEmitter.completeWithError(e);
|
||||
}
|
||||
@Override
|
||||
public void onError(Throwable e) {
|
||||
responseBodyEmitter.completeWithError(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCompleted() {
|
||||
if(!completed) {
|
||||
completed = true;
|
||||
responseBodyEmitter.complete();
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void onCompleted() {
|
||||
if(!completed) {
|
||||
completed = true;
|
||||
responseBodyEmitter.complete();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
subscription.unsubscribe();
|
||||
}
|
||||
@Override
|
||||
public void run() {
|
||||
subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user