Compare commits

...

121 commits
v0.4.0 ... main

Author SHA1 Message Date
d916f2134b chore(deps): update dependency spark to v2.2.29
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-09-16 12:23:33 +12:00
ffe7d4a2ca chore(deps): update dependency spark to v2.2.28
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-09-15 10:27:52 +12:00
8836588248 chore(deps): update dependency spark to v2.2.27
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-09-12 12:46:36 +12:00
1c79d2295f chore(deps): update dependency mimic to v1.10.1
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-09-11 21:23:35 +12:00
1882a7fbe7 chore(deps): update dependency spark to v2.2.25
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-09-11 00:25:48 +12:00
a72e694ed4
chore: unlock unused dependencies.
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is failing
2024-09-10 11:22:24 +12:00
46c9cede96 chore(deps): update dependency spark to v2.2.24
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is failing
2024-09-10 00:23:48 +12:00
3fb227f5d9 chore(deps): update dependency mimic to v1.10.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-09-02 22:26:28 +12:00
d74c8e3cb3 chore(deps): update dependency spark to v2.2.23
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-08-30 10:22:43 +12:00
fad27f142e chore: release version v0.6.1
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2024-08-27 03:54:31 +00:00
b96516405e
refactor(WebSocketProxy): Correctly traverse incoming messages to set the response.
All checks were successful
continuous-integration/drone/push Build is passing
2024-08-27 15:53:17 +12:00
a191077cc4
fix(Proxy): don't crash for responses with no body. 2024-08-27 15:53:17 +12:00
de29cb2b51 chore(deps): update dependency telemetry to v1.3.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-08-23 04:35:50 +12:00
d9fe59958e chore: release version v0.6.0
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2024-08-20 04:45:52 +00:00
07c41153f5 feat: Add request telemetry (#114)
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #114
Co-authored-by: James Harton <james@harton.nz>
Co-committed-by: James Harton <james@harton.nz>
2024-08-20 16:44:08 +12:00
dfe52b6e74 chore(deps): update dependency spark to v2.2.22
Some checks failed
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is failing
2024-08-20 14:38:38 +12:00
c5a337ca4a
docs: Update readme.
All checks were successful
continuous-integration/drone/push Build is passing
2024-08-18 09:58:18 +12:00
c440a88bdc chore: release version v0.5.0
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2024-08-17 06:54:11 +00:00
4ad256acdd
feat: Add support for proxying websockets.
All checks were successful
continuous-integration/drone/push Build is passing
2024-08-17 18:50:43 +12:00
de160bd70c chore(deps): update dependency spark to v2.2.21
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-08-15 06:19:21 +12:00
11b8444b96
chore: update cheatsheet.
All checks were successful
continuous-integration/drone/push Build is passing
2024-08-14 15:44:07 +12:00
60664ff5cc chore(deps): update dependency spark to v2.2.20
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-08-14 14:21:51 +12:00
d8e7e0c3a0 chore(deps): update dependency spark to v2.2.19
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2024-08-14 11:20:45 +12:00
e43c31997e chore(deps): update dependency spark to v2.2.18
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-08-14 08:18:53 +12:00
f739d4b7e1 chore(deps): update dependency spark to v2.2.17
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-08-14 05:21:13 +12:00
cd489acd63 chore(deps): update dependency spark to v2.2.16
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-08-14 01:17:31 +12:00
09433f8e26 chore(deps): update dependency spark to v2.2.14
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is passing
2024-08-13 14:17:38 +12:00
adc2453081 chore(deps): update dependency spark to v2.2.12
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is passing
2024-08-13 13:16:28 +12:00
cac6f82746 chore(deps): update dependency spark to v2.2.11
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-08-02 18:36:41 +12:00
8cf2027f28 chore(deps): update dependency bandit to v1.5.7
Some checks failed
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is failing
2024-08-02 18:28:38 +12:00
638badbba8 chore(deps): update dependency bandit to v1.5.5 (#102)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [bandit](https://hex.pm/packages/bandit) | patch | `1.5.3` -> `1.5.5` |

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled because a matching PR was automerged previously.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOC43LjEiLCJ1cGRhdGVkSW5WZXIiOiIzOC44LjEiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbInJlbm92YXRlIl19-->

Reviewed-on: #102
Co-authored-by: Renovate Bot <bot@harton.nz>
Co-committed-by: Renovate Bot <bot@harton.nz>
2024-07-27 21:07:06 +12:00
a701a200b0
chore: remove unused dependencies.
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-07-27 15:31:09 +12:00
b11b37078e chore(deps): update dependency spark to v2.2.10
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is failing
2024-07-27 06:38:13 +12:00
7ffec1bed6 chore(deps): update dependency mimic to v1.9.0
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-07-18 20:32:31 +12:00
19f4a9b387 chore(deps): update dependency mix_audit to v2.1.4
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-07-17 02:34:26 +12:00
c016c5a8ae chore(deps): update dependency earmark to v1.4.47
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-07-13 06:42:06 +12:00
59ae3a59cf chore(deps): update dependency erlang to v27.0.1
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is passing
2024-07-11 01:42:20 +12:00
331827d6b4 chore(deps): update dependency ex_doc to v0.34.2
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is passing
2024-07-09 03:46:20 +12:00
c6b7c98930 chore(deps): update dependency elixir to v1.17.2
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-07-07 09:40:47 +12:00
08976af06c chore(deps): update dependency spark to v2.2.7
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-07-03 10:37:42 +12:00
69a32accd4 chore(deps): update dependency castore to v1.0.8
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-07-02 19:21:25 +12:00
0dd15ad75e chore(deps): update dependency mint to v1.6.2
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-07-02 04:12:11 +12:00
6bf61d35f1 chore(deps): update dependency spark to v2.2.6
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-06-29 10:41:44 +12:00
ec239d1e57 chore(deps): update dependency spark to v2.2.5
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-06-26 07:39:37 +12:00
bbc6f05d20 chore(deps): update dependency mimic to v1.8.2
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-06-23 22:40:48 +12:00
82e4b461eb chore: release version v0.4.1
Some checks reported errors
continuous-integration/drone/push Build encountered an error
continuous-integration/drone/tag Build encountered an error
2024-06-23 08:44:45 +00:00
384257a33f
fix: incorrect handling of Mint stream failure.
All checks were successful
continuous-integration/drone Build is passing
continuous-integration/drone/push Build is passing
2024-06-23 16:00:51 +12:00
d4d38b7b34 chore(deps): update dependency elixir to v1.17.1
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2024-06-23 01:40:13 +12:00
57e0f63ee8 chore(deps): update dependency mimic to v1.8.1
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-06-23 00:42:06 +12:00
6eb6fd6a1f chore(deps): update dependency mimic to v1.8.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-06-22 22:42:33 +12:00
90c60759ca
chore: ignore a dialyzer warning I just can't wrap my head around.
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-06-21 19:28:37 +12:00
886e032c05 chore(deps): update dependency spark to v2.2.4
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2024-06-21 06:22:28 +12:00
3545d01262 chore(deps): update dependency ex_doc to v0.34.1
All checks were successful
continuous-integration/drone/push Build is passing
2024-06-21 02:42:13 +12:00
e1e796e234 chore(deps): update dependency bandit to v1.5.5
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-06-21 02:24:07 +12:00
d7d16fd925 chore(deps): update dependency spark to v2.2.1
All checks were successful
continuous-integration/drone/push Build is passing
2024-06-15 13:33:20 +12:00
0c9137cfe9 chore(deps): update dependency credo to v1.7.7
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-06-15 12:55:44 +12:00
204dd61258 chore(deps): update dependency spark to v2.1.24
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-06-11 03:21:52 +12:00
51d100898c chore(deps): update dependency bandit to v1.5.3
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-06-08 06:20:41 +12:00
b6e2337dab chore(deps): update dependency spark to v2.1.23
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-06-08 02:23:58 +12:00
304fd0bcd4 chore(deps): update dependency ex_doc to v0.34.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-05-31 01:26:40 +12:00
1008da98a4 chore(deps): update dependency nimble_options to v1.1.1
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-05-25 23:17:11 +12:00
5d8be9ae42 chore(deps): update dependency ex_doc to v0.33.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-05-22 01:22:56 +12:00
2be599db1b chore(deps): update dependency elixir to v1.16.3
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-05-21 11:23:57 +12:00
e32b2a868d chore(deps): update dependency erlang to v27
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-05-20 21:45:26 +12:00
6009a19705 chore(deps): update dependency plug to v1.16.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-05-18 21:45:45 +12:00
055b0ca2d5 chore(deps): update dependency spark to v2.1.22
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-05-15 04:19:50 +12:00
b8b5623a2f chore(deps): update dependency spark to v2.1.21
All checks were successful
continuous-integration/drone/push Build is passing
2024-05-11 11:35:59 +12:00
4766d48dcb chore(deps): update dependency git_ops to v2.6.1
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-05-11 10:42:14 +12:00
db3abee0fb chore(deps): update dependency bandit to v1.5.2
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-05-11 05:45:31 +12:00
99aaa5d57d chore(deps): update dependency bandit to v1.5.1
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-05-11 03:36:03 +12:00
4ce0d89652 chore(deps): update dependency ex_doc to v0.32.2
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-05-10 23:40:33 +12:00
d78dca56f6 chore(deps): update dependency credo to v1.7.6
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-05-10 00:00:52 +12:00
989d48c46d chore(deps): update dependency erlang to v26.2.5
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-05-03 03:37:37 +12:00
27858f1474 chore(deps): update dependency spark to v2.1.20
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-04-22 05:32:32 +12:00
7093ae3517 chore(deps): update dependency spark to v2.1.19
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-04-22 03:35:27 +12:00
6e63fa3961 chore(deps): update dependency bandit to v1.5.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-04-22 01:32:24 +12:00
fe61ed4e1a chore(deps): update dependency castore to v1.0.7
Some checks failed
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is failing
2024-04-20 17:32:43 +12:00
7e5ec8c11c chore(deps): update dependency ex_doc to v0.32.1
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-04-13 08:36:52 +12:00
ac3440d512 chore(deps): update dependency spark to v2.1.18
Some checks failed
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is failing
2024-04-13 06:33:15 +12:00
bbf213825b chore(deps): update dependency erlang to v26.2.4
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-04-13 00:37:35 +12:00
5a411f54ee chore(deps): update dependency spark to v2.1.17
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-04-12 05:31:37 +12:00
f3c49db9db chore(deps): update dependency spark to v2.1.15
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-04-11 04:59:18 +12:00
c6f84bb1c5 chore(deps): update dependency spark to v2.1.14
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-04-11 02:31:26 +12:00
5c70adaaff chore(deps): update dependency ex_doc to v0.32.0
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-04-10 21:34:21 +12:00
35dce07e77 chore(deps): update dependency spark to v2.1.13 (#50)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [spark](https://hex.pm/packages/spark) | patch | `2.1.11` -> `2.1.13` |

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIwLjAuMC1zZW1hbnRpYy1yZWxlYXNlIiwidXBkYXRlZEluVmVyIjoiMC4wLjAtc2VtYW50aWMtcmVsZWFzZSIsInRhcmdldEJyYW5jaCI6Im1haW4ifQ==-->

Reviewed-on: #50
Co-authored-by: Renovate Bot <bot@harton.nz>
Co-committed-by: Renovate Bot <bot@harton.nz>
2024-04-06 12:48:47 +13:00
4747c50673 chore(deps): update dependency bandit to v1.4.2
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-04-03 06:25:01 +13:00
b35d678050 chore(deps): update dependency spark to v2.1.11
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-03-30 10:30:14 +13:00
8791e1228d chore(deps): update dependency spark to v2.1.10
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-03-30 08:00:51 +13:00
5d491362d2 chore(deps): update dependency spark to v2.1.9
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-03-29 11:41:51 +13:00
2e9f41c00d chore(deps): update dependency spark to v2.1.8
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-03-28 12:16:44 +13:00
3ed6b64e80 chore(deps): update dependency bandit to v1.4.1 (#45)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [bandit](https://hex.pm/packages/bandit) | minor | `1.3.0` -> `1.4.1` |

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIwLjAuMC1zZW1hbnRpYy1yZWxlYXNlIiwidXBkYXRlZEluVmVyIjoiMC4wLjAtc2VtYW50aWMtcmVsZWFzZSIsInRhcmdldEJyYW5jaCI6Im1haW4ifQ==-->

Reviewed-on: #45
Co-authored-by: Renovate Bot <bot@harton.nz>
Co-committed-by: Renovate Bot <bot@harton.nz>
2024-03-28 11:37:01 +13:00
2b79e3cb10 chore(deps): update dependency spark to v2.1.4
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-03-26 11:15:52 +13:00
25fb114808 chore(deps): update dependency spark to v2.1.3
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-03-21 02:25:50 +13:00
c672e2c4f4 chore(deps): update dependency mix_audit to v2.1.3
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-03-19 16:10:11 +13:00
7e2f0f2182 chore(deps): update dependency spark to v2.1.2
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-03-19 07:24:15 +13:00
41ac07b8f1 chore(deps): update dependency spark to v2.1.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-03-16 05:33:49 +13:00
0d186e834a
chore: fix docs release.
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-14 19:30:17 +13:00
306a0d7935 chore(deps): update dependency ip to v2.0.3
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-03-14 17:19:59 +13:00
2736fdbbd1 chore(deps): update dependency ex_doc to v0.31.2
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-03-14 16:07:40 +13:00
e48f26dd85 chore(deps): update dependency bandit to v1.3.0
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-14 16:01:40 +13:00
b59af873e8 chore(deps): update dependency mix_audit to v2.1.2
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-03-14 15:25:04 +13:00
5cfb9a4a7d chore(deps): update dependency ip to v2.0.2
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-14 15:05:11 +13:00
52632fe88e chore(deps): update dependency credo to v1.7.5
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-14 15:04:38 +13:00
d95e23d727 chore(deps): update dependency dialyxir to v1.4.3
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-03-14 00:54:22 +00:00
25c9003193 chore(deps): update dependency plug to v1.15.3
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-14 13:33:30 +13:00
ccdfce9850 chore(deps): update dependency castore to v1.0.6
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-03-13 21:20:24 +00:00
d57e6a0b7f chore(deps): update dependency elixir to v1.16.2
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-03-11 00:37:49 +13:00
5ec7cf267a
chore: fix typo in readme
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-08 14:48:42 +13:00
48bc11159a chore(deps): update dependency erlang to v26.2.3
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-03-07 22:33:49 +13:00
3383898512
docs: Update Readme.
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-06 20:26:35 +13:00
ca1a344057
docs(README): Add GitHub mirror link.
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-06 18:05:41 +13:00
3312d02642
chore: Update hex package links.
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-06 18:03:25 +13:00
e4224a0daf
chore(deps): update dependency spark to v2
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-03-03 20:13:16 +13:00
49c30c4dde chore(deps): update dependency ex_check to ~> 0.16
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-03-01 23:30:58 +13:00
3ebb9843da chore(deps): update dependency faker to ~> 0.18
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-02-29 19:45:15 +13:00
f192b9fd5f chore(deps): update dependency erlang to v26.2.2
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-02-09 10:48:53 +13:00
76f2a299f8 chore: Update forgejo hostname.
All checks were successful
continuous-integration/drone/push Build is passing
2024-02-05 14:58:36 +13:00
37a064c507 chore: make more tests synchronous. 2024-02-05 14:54:27 +13:00
8358d2de28 chore(deps): update dependency elixir to v1.16.1
Some checks failed
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is failing
2024-01-31 23:22:35 +13:00
c142bdbc1a chore(deps): update dependency elixir to v1.16.0 (#13)
All checks were successful
continuous-integration/drone/push Build is passing
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [elixir](https://elixir-lang.org/) ([source](https://github.com/elixir-lang/elixir)) | minor | `1.15.7` -> `1.16.0` |

---

### Release Notes

<details>
<summary>elixir-lang/elixir (elixir)</summary>

### [`v1.16.0`](https://github.com/elixir-lang/elixir/releases/tag/v1.16.0)

[Compare Source](https://github.com/elixir-lang/elixir/compare/v1.15.7...v1.16.0)

Official announcement: https://elixir-lang.org/blog/2023/12/22/elixir-v1-16-0-released/

##### 1. Enhancements

##### EEx

-   \[EEx] Include relative file information in diagnostics

##### Elixir

-   \[Code] Add `:emit_warnings` for `Code.string_to_quoted/2`
-   \[Code] Automatically include columns in parsing options
-   \[Code] Introduce `MismatchedDelimiterError` for handling mismatched delimiter exceptions
-   \[Code.Fragment] Handle anonymous calls in fragments
-   \[Code.Formatter] Trim trailing whitespace on heredocs with `\r\n`
-   \[File] Add `:offset` option to `File.stream!/2`
-   \[Kernel] Auto infer size of matched variable in bitstrings
-   \[Kernel] Preserve column information when translating typespecs
-   \[Kernel] Suggest module names based on suffix and casing errors when the module does not exist in `UndefinedFunctionError`
-   \[Kernel.ParallelCompiler] Introduce `Kernel.ParallelCompiler.pmap/2` to compile multiple additional entries in parallel
-   \[Kernel.SpecialForms] Warn if `True`/`False`/`Nil` are used as aliases and there is no such alias
-   \[Macro] Add `Macro.compile_apply/4`
-   \[Module] Add support for `@nifs` annotation from Erlang/OTP 25
-   \[Module] Add support for missing `@dialyzer` configuration
-   \[String] Update to Unicode 15.1.0
-   \[String] Add `String.replace_invalid/2`
-   \[Task] Add `:limit` option to `Task.yield_many/2`

##### Logger

-   \[Logger] Add `Logger.levels/0`

##### Mix

-   \[mix] Add `MIX_PROFILE` to profile a list of comma separated tasks
-   \[mix archive.install] Support `--sparse` option
-   \[mix compile.app] Warn if both `:applications` and `:extra_applications` are used
-   \[mix compile.elixir] Pass original exception down to diagnostic `:details` when possible
-   \[mix compile.elixir] Optimize scenario where there are thousands of files in `lib/` and one of them is changed
-   \[mix deps.clean] Emit a warning instead of crashing when a dependency cannot be removed
-   \[mix escript.install] Support `--sparse` option
-   \[mix release] Include `include/` directory in releases
-   \[mix test] Allow testing multiple file:line at once, such as `mix test test/foo_test.exs:13 test/bar_test.exs:27`

##### 2. Bug fixes

##### Elixir

-   \[Code] Keep quotes for atom keys in formatter
-   \[Code.Fragment] Fix crash in `Code.Fragment.surround_context/2` when matching on `->`
-   \[IO] Raise when using `IO.binwrite/2` on terminated device (mirroring `IO.write/2`)
-   \[Kernel] Do not expand aliases recursively (the alias stored in Macro.Env is already expanded)
-   \[Kernel] Ensure `dbg` module is a compile-time dependency
-   \[Kernel] Warn when a private function or macro uses `unquote/1` and the function/macro itself is unused
-   \[Kernel] Re-enabled compiler optimizations for top level functions in scripts (disabled in v1.14.0 but shouldn't impact most programs)
-   \[Kernel] Do not define an alias for nested modules starting with `Elixir.` in their definition
-   \[Kernel.ParallelCompiler] Consider a module has been defined in `@after_compile` callbacks to avoid deadlocks
-   \[Macro] Address exception on `Macro.to_string/1` for certain ASTs
-   \[Path] Lazily evaluate `File.cwd!/0` in `Path.expand/1` and `Path.absname/1`
-   \[Path] Ensure `Path.relative_to/2` returns a relative path when the given argument does not share a common prefix with `cwd`

##### ExUnit

-   \[ExUnit] Raise on incorrectly dedented doctests

##### IEx

-   \[IEx.Pry] Fix prying functions with only literals in their body

##### Mix

-   \[mix archive.install] Restore code paths after `mix archive.install`
-   \[mix compile] Ensure files with duplicate modules are recompiled whenever any of the files change
-   \[mix compile] Update Mix compiler diagnostics documentation and typespecs to match the Elixir compiler behaviour where both lines and columns start from one (before it inaccurately said that columns started from zero)
-   \[mix escript.install] Restore code paths after `mix escript.install`

##### 3. Soft deprecations (no warnings emitted)

##### Elixir

-   \[File] Deprecate `File.stream!(file, options, line_or_bytes)` in favor of keeping the options as last argument, as in `File.stream!(file, line_or_bytes, options)`
-   \[Kernel.ParallelCompiler] Deprecate `Kernel.ParallelCompiler.async/1` in favor of `Kernel.ParallelCompiler.pmap/2`
-   \[Path] Deprecate `Path.safe_relative_to/2` in favor of `Path.safe_relative/2`

##### Mix

-   \[mix compile] Returning a four-element tuple as a position in `Mix.Task.Compiler.Diagnostic`

##### 4. Hard deprecations

##### Elixir

-   \[Date] Deprecate inferring a range with negative step, call `Date.range/3` with a negative step instead
-   \[Enum] Deprecate passing a range with negative step on `Enum.slice/2`, give `first..last//1` instead
-   \[Kernel] `~R/.../` is deprecated in favor of `~r/.../`. This is because `~R/.../` still allowed escape codes, which did not fit the definition of uppercase sigils
-   \[String] Deprecate passing a range with negative step on `String.slice/2`, give `first..last//1` instead

##### ExUnit

-   \[ExUnit.Formatter] Deprecate `format_time/2`, use `format_times/1` instead

##### Mix

-   \[mix compile.leex] Require `:leex` to be added as a compiler to run the `leex` compiler
-   \[mix compile.yecc] Require `:yecc` to be added as a compiler to run the `yecc` compiler

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy4xMDcuMCIsInVwZGF0ZWRJblZlciI6IjM3LjEyNy4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiJ9-->

Reviewed-on: https://code.harton.nz/james/wayfarer/pulls/13
Co-authored-by: Renovate Bot <bot@harton.nz>
Co-committed-by: Renovate Bot <bot@harton.nz>
2024-01-15 10:37:57 +13:00
545384b149 chore(deps): update dependency erlang to v26.2.1
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-12-19 04:10:27 +13:00
33 changed files with 2961 additions and 2411 deletions

5
.dialyzer_ignore.exs Normal file
View file

@ -0,0 +1,5 @@
[
{"lib/wayfarer/server/proxy.ex", :unknown_function},
{"lib/wayfarer/target/check.ex", :unknown_function},
{"test/support/http_request.ex", :unknown_function}
]

View file

@ -49,7 +49,7 @@ steps:
- .rebar3 - .rebar3
- name: install dependencies - name: install dependencies
image: code.harton.nz/james/asdf_container:latest image: harton.dev/james/asdf_container:latest
pull: "always" pull: "always"
environment: environment:
MIX_ENV: test MIX_ENV: test
@ -122,7 +122,7 @@ steps:
- .rebar3 - .rebar3
- name: mix compile - name: mix compile
image: code.harton.nz/james/asdf_container:latest image: harton.dev/james/asdf_container:latest
environment: environment:
MIX_ENV: test MIX_ENV: test
HEX_HOME: /drone/src/.hex HEX_HOME: /drone/src/.hex
@ -135,7 +135,7 @@ steps:
- asdf mix compile --warnings-as-errors - asdf mix compile --warnings-as-errors
- name: mix test - name: mix test
image: code.harton.nz/james/asdf_container:latest image: harton.dev/james/asdf_container:latest
environment: environment:
MIX_ENV: test MIX_ENV: test
HEX_HOME: /drone/src/.hex HEX_HOME: /drone/src/.hex
@ -148,7 +148,7 @@ steps:
- asdf mix test - asdf mix test
- name: mix credo - name: mix credo
image: code.harton.nz/james/asdf_container:latest image: harton.dev/james/asdf_container:latest
environment: environment:
MIX_ENV: test MIX_ENV: test
HEX_HOME: /drone/src/.hex HEX_HOME: /drone/src/.hex
@ -161,7 +161,7 @@ steps:
- asdf mix credo --strict - asdf mix credo --strict
- name: mix hex.audit - name: mix hex.audit
image: code.harton.nz/james/asdf_container:latest image: harton.dev/james/asdf_container:latest
environment: environment:
MIX_ENV: test MIX_ENV: test
HEX_HOME: /drone/src/.hex HEX_HOME: /drone/src/.hex
@ -174,7 +174,7 @@ steps:
- asdf mix hex.audit - asdf mix hex.audit
- name: mix format - name: mix format
image: code.harton.nz/james/asdf_container:latest image: harton.dev/james/asdf_container:latest
environment: environment:
MIX_ENV: test MIX_ENV: test
HEX_HOME: /drone/src/.hex HEX_HOME: /drone/src/.hex
@ -187,7 +187,7 @@ steps:
- asdf mix format --check-formatted - asdf mix format --check-formatted
- name: mix deps.unlock - name: mix deps.unlock
image: code.harton.nz/james/asdf_container:latest image: harton.dev/james/asdf_container:latest
environment: environment:
MIX_ENV: test MIX_ENV: test
HEX_HOME: /drone/src/.hex HEX_HOME: /drone/src/.hex
@ -200,7 +200,7 @@ steps:
- asdf mix deps.unlock --check-unused - asdf mix deps.unlock --check-unused
- name: mix doctor - name: mix doctor
image: code.harton.nz/james/asdf_container:latest image: harton.dev/james/asdf_container:latest
environment: environment:
MIX_ENV: test MIX_ENV: test
HEX_HOME: /drone/src/.hex HEX_HOME: /drone/src/.hex
@ -213,7 +213,7 @@ steps:
- asdf mix doctor --full - asdf mix doctor --full
- name: mix dialyzer - name: mix dialyzer
image: code.harton.nz/james/asdf_container:latest image: harton.dev/james/asdf_container:latest
environment: environment:
MIX_ENV: test MIX_ENV: test
HEX_HOME: /drone/src/.hex HEX_HOME: /drone/src/.hex
@ -226,7 +226,7 @@ steps:
- asdf mix dialyzer - asdf mix dialyzer
- name: mix git_ops.check_message - name: mix git_ops.check_message
image: code.harton.nz/james/asdf_container:latest image: harton.dev/james/asdf_container:latest
environment: environment:
MIX_ENV: test MIX_ENV: test
HEX_HOME: /drone/src/.hex HEX_HOME: /drone/src/.hex
@ -240,7 +240,7 @@ steps:
- asdf mix git_ops.check_message .last_commit_message - asdf mix git_ops.check_message .last_commit_message
- name: mix git_ops.release - name: mix git_ops.release
image: code.harton.nz/james/asdf_container:latest image: harton.dev/james/asdf_container:latest
when: when:
branch: branch:
- main - main
@ -280,7 +280,7 @@ steps:
- fi - fi
- name: build artifacts - name: build artifacts
image: code.harton.nz/james/asdf_container:latest image: harton.dev/james/asdf_container:latest
when: when:
event: event:
- tag - tag
@ -327,7 +327,7 @@ steps:
settings: settings:
api_key: api_key:
from_secret: DRONE_TOKEN from_secret: DRONE_TOKEN
base_url: https://code.harton.nz base_url: https://harton.dev
files: artifacts/*.tar.gz files: artifacts/*.tar.gz
checksum: sha256 checksum: sha256
@ -351,12 +351,11 @@ steps:
commands: commands:
- mc alias set store $${S3_ENDPOINT} $${ACCESS_KEY} $${SECRET_KEY} - mc alias set store $${S3_ENDPOINT} $${ACCESS_KEY} $${SECRET_KEY}
- mc mb -p store/docs.harton.nz - mc mb -p store/docs.harton.nz
- mc anonymous set download store/docs.harton.nz
- mc mirror --overwrite doc/ store/docs.harton.nz/$${DRONE_REPO}/$${DRONE_TAG} - mc mirror --overwrite doc/ store/docs.harton.nz/$${DRONE_REPO}/$${DRONE_TAG}
- mc mirror --overwrite doc/ store/docs.harton.nz/$${DRONE_REPO} - mc mirror --overwrite doc/ store/docs.harton.nz/$${DRONE_REPO}
- name: hex release - name: hex release
image: code.harton.nz/james/asdf_container:latest image: harton.dev/james/asdf_container:latest
when: when:
event: event:
- tag - tag

View file

@ -38,7 +38,12 @@ spark_locals_without_parens = [
targets: 1, targets: 1,
thousand_island_options: 1, thousand_island_options: 1,
threshold: 1, threshold: 1,
websocket_options: 1 transport: 1,
websocket_options: 1,
ws: 2,
ws: 3,
wss: 2,
wss: 3
] ]
[ [

View file

@ -1,3 +1,3 @@
erlang 26.1.2 erlang 27.0.1
elixir 1.15.7 elixir 1.17.2
hey 0.1.4 hey 0.1.4

3
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,3 @@
{
"cSpell.words": ["ntoa"]
}

View file

@ -5,7 +5,43 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline
<!-- changelog --> <!-- changelog -->
## [v0.4.0](https://code.harton.nz/james/wayfarer/compare/v0.3.0...v0.4.0) (2023-11-19) ## [v0.6.1](https://harton.dev/james/wayfarer/compare/v0.6.0...v0.6.1) (2024-08-27)
### Bug Fixes:
* Proxy: don't crash for responses with no body.
## [v0.6.0](https://harton.dev/james/wayfarer/compare/v0.5.0...v0.6.0) (2024-08-20)
### Features:
* Add request telemetry (#114)
## [v0.5.0](https://harton.dev/james/wayfarer/compare/v0.4.0...v0.5.0) (2024-08-17)
### Features:
* Add support for proxying websockets.
## [v0.4.1](https://harton.dev/james/wayfarer/compare/v0.4.0...v0.4.1) (2024-06-23)
### Bug Fixes:
* incorrect handling of Mint stream failure.
## [v0.4.0](https://harton.dev/james/wayfarer/compare/v0.3.0...v0.4.0) (2023-11-19)
@ -14,7 +50,7 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline
* add proxying. (#7) * add proxying. (#7)
## [v0.3.0](https://code.harton.nz/james/wayfarer/compare/v0.2.0...v0.3.0) (2023-10-14) ## [v0.3.0](https://harton.dev/james/wayfarer/compare/v0.2.0...v0.3.0) (2023-10-14)
@ -27,7 +63,7 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline
* Listener: Register listeners with scheme, address and port. * Listener: Register listeners with scheme, address and port.
## [v0.2.0](https://code.harton.nz/james/wayfarer/compare/v0.1.0...v0.2.0) (2023-10-14) ## [v0.2.0](https://harton.dev/james/wayfarer/compare/v0.1.0...v0.2.0) (2023-10-14)
@ -36,7 +72,7 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline
* Add ability to start and stop HTTP listeners. (#1) * Add ability to start and stop HTTP listeners. (#1)
## [v0.1.0](https://code.harton.nz/james/wayfarer/compare/v0.1.0...v0.1.0) (2023-10-13) ## [v0.1.0](https://harton.dev/james/wayfarer/compare/v0.1.0...v0.1.0) (2023-10-13)

View file

@ -1,6 +1,6 @@
# Wayfarer # Wayfarer
[![Build Status](https://drone.harton.nz/api/badges/james/wayfarer/status.svg?ref=refs/heads/main)](https://drone.harton.nz/james/wayfarer) [![Build Status](https://drone.harton.dev/api/badges/james/wayfarer/status.svg?ref=refs/heads/main)](https://drone.harton.dev/james/wayfarer)
[![Hippocratic License HL3-FULL](https://img.shields.io/static/v1?label=Hippocratic%20License&message=HL3-FULL&labelColor=5e2751&color=bc8c3d)](https://firstdonoharm.dev/version/3/0/full.html) [![Hippocratic License HL3-FULL](https://img.shields.io/static/v1?label=Hippocratic%20License&message=HL3-FULL&labelColor=5e2751&color=bc8c3d)](https://firstdonoharm.dev/version/3/0/full.html)
Wayfarer is a runtime-configurable HTTP reverse proxy using Wayfarer is a runtime-configurable HTTP reverse proxy using
@ -9,22 +9,31 @@ Wayfarer is a runtime-configurable HTTP reverse proxy using
## Status ## Status
Wayfarer is yet to handle it's first HTTP request. Please hold. Wayfarer is able to proxy HTTP/1, HTTP/2 and WebSocket requests. There are
probably still edge cases and bugs, but it can be used.
## Installation ## Installation
Wayfarer is not yet available on Hex, so you will need to add it as a Git Wayfarer is [available in Hex](https://hex.pm/packages/wayfarer), the package
dependency in your app: can be installed by adding `wayfarer` to your list of dependencies in `mix.exs`:
```elixir ```elixir
def deps do def deps do
[ [
{:wayfarer, git: "https://code.harton.nz/james/wayfarer.git", tag: "v0.1.0"} {:wayfarer, "~> 0.4.1"}
] ]
end end
``` ```
Documentation for `main` is always available on [my docs site](https://docs.harton.nz/james/wayfarer/Wayfarer.html). Documentation for the latest release can be found on
[HexDocs](https://hexdocs.pm/wayfarer) and for the `main` branch on
[docs.harton.nz](https://docs.harton.nz/james/wayfarer).
## Github Mirror
This repository is mirrored [on Github](https://github.com/jimsynz/wayfarer)
from it's primary location [on my Forgejo instance](https://harton.dev/james/wayfarer).
Feel free to raise issues and open PRs on Github.
## License ## License

View file

@ -3,7 +3,7 @@ import Config
config :git_ops, config :git_ops,
mix_project: Mix.Project.get!(), mix_project: Mix.Project.get!(),
changelog_file: "CHANGELOG.md", changelog_file: "CHANGELOG.md",
repository_url: "https://code.harton.nz/james/wayfarer", repository_url: "https://harton.dev/james/wayfarer",
manage_mix_version?: true, manage_mix_version?: true,
version_tag_prefix: "v", version_tag_prefix: "v",
manage_readme_version: "README.md" manage_readme_version: "README.md"

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -85,7 +85,7 @@ defmodule Wayfarer.Dsl.HealthCheck do
doc: "Path" doc: "Path"
], ],
success_codes: [ success_codes: [
type: {:wrap_list, {:struct, Range}}, type: {:wrap_list, {:or, [{:struct, Range}, {:in, 100..500}]}},
required: false, required: false,
default: @defaults[:success_codes], default: @defaults[:success_codes],
doc: "HTTP status codes which are considered successful." doc: "HTTP status codes which are considered successful."
@ -125,22 +125,34 @@ defmodule Wayfarer.Dsl.HealthCheck do
@doc false @doc false
@spec transform(t) :: {:ok, t} | {:error, any} @spec transform(t) :: {:ok, t} | {:error, any}
def transform(check) do def transform(check) do
with :ok <- verify_success_codes(check.success_codes) do with {:ok, success_codes} <- transform_success_codes(check.success_codes) do
maybe_set_name(check) maybe_set_name(%{check | success_codes: success_codes})
end end
end end
defguardp is_valid_status_code?(code) when is_integer(code) and code >= 100 and code <= 599
defguardp is_valid_range?(range) defguardp is_valid_range?(range)
when is_struct(range, Range) and is_integer(range.first) and range.first >= 100 and when is_struct(range, Range) and is_valid_status_code?(range.first) and
is_integer(range.last) and range.last <= 500 is_valid_status_code?(range.last)
defp verify_success_codes(range) when is_valid_range?(range), do: :ok defp transform_success_codes(range) when is_valid_range?(range), do: {:ok, [range]}
defp verify_success_codes([]), do: :ok defp transform_success_codes([]), do: {:ok, []}
defp verify_success_codes([head | tail]) when is_valid_range?(head), defp transform_success_codes([head | tail]) when is_valid_range?(head) do
do: verify_success_codes(tail) with {:ok, tail} <- transform_success_codes(tail) do
{:ok, [head | tail]}
end
end
defp verify_success_codes([range | _]), defp transform_success_codes([head | tail])
when is_integer(head) and is_valid_status_code?(head) do
with {:ok, tail} <- transform_success_codes(tail) do
{:ok, [head..head | tail]}
end
end
defp transform_success_codes([range | _]),
do: {:error, "Value `#{inspect(range)}` is not valid. Must be a range between 100..599"} do: {:error, "Value `#{inspect(range)}` is not valid. Must be a range between 100..599"}
defp maybe_set_name(check) when is_binary(check.name), do: {:ok, check} defp maybe_set_name(check) when is_binary(check.name), do: {:ok, check}

View file

@ -3,7 +3,7 @@ defmodule Wayfarer.Dsl.Target do
A struct for storing a target generated by the DSL. A struct for storing a target generated by the DSL.
""" """
alias Spark.{Dsl.Entity, OptionsHelpers} alias Spark.{Dsl.Entity, Options}
alias Wayfarer.Dsl.{HealthCheck, HealthChecks} alias Wayfarer.Dsl.{HealthCheck, HealthChecks}
alias Wayfarer.Utils alias Wayfarer.Utils
@ -13,6 +13,7 @@ defmodule Wayfarer.Dsl.Target do
name: nil, name: nil,
port: nil, port: nil,
scheme: :http, scheme: :http,
transport: :auto,
uri: nil uri: nil
@type t :: %__MODULE__{ @type t :: %__MODULE__{
@ -21,7 +22,8 @@ defmodule Wayfarer.Dsl.Target do
module: nil | module, module: nil | module,
name: nil | String.t(), name: nil | String.t(),
port: :inet.port_number(), port: :inet.port_number(),
scheme: :http | :https | :plug, scheme: :http | :https | :plug | :ws | :wss,
transport: :http1 | :http2 | :auto,
uri: URI.t() uri: URI.t()
} }
@ -40,6 +42,12 @@ defmodule Wayfarer.Dsl.Target do
type: :pos_integer, type: :pos_integer,
required: true, required: true,
doc: "The TCP port on which to listen for incoming connections." doc: "The TCP port on which to listen for incoming connections."
],
transport: [
type: {:in, [:http1, :http2, :auto]},
required: false,
default: :auto,
doc: "Which HTTP protocol to use."
] ]
] ]
@ -81,6 +89,28 @@ defmodule Wayfarer.Dsl.Target do
], ],
auto_set_fields: [scheme: :plug], auto_set_fields: [scheme: :plug],
args: [:module] args: [:module]
},
%Entity{
name: :ws,
target: __MODULE__,
schema: @shared_schema,
auto_set_fields: [scheme: :ws],
args: [:address, :port],
imports: [IP.Sigil],
transform: {__MODULE__, :transform, []},
entities: [health_checks: HealthChecks.entities()],
singleton_entity_keys: [:health_checks]
},
%Entity{
name: :wss,
target: __MODULE__,
schema: @shared_schema,
auto_set_fields: [scheme: :wss],
args: [:address, :port],
imports: [IP.Sigil],
transform: {__MODULE__, :transform, []},
entities: [health_checks: HealthChecks.entities()],
singleton_entity_keys: [:health_checks]
} }
] ]
end end
@ -98,13 +128,13 @@ defmodule Wayfarer.Dsl.Target do
@doc false @doc false
def schema do def schema do
@shared_schema @shared_schema
|> OptionsHelpers.make_optional!(:address) |> Options.Helpers.make_optional!(:address)
|> OptionsHelpers.make_optional!(:port) |> Options.Helpers.make_optional!(:port)
|> Keyword.merge( |> Keyword.merge(
scheme: [ scheme: [
type: {:in, [:http, :https, :plug]}, type: {:in, [:http, :https, :plug, :ws, :wss]},
required: true, required: true,
doc: "The protocol used to talk to the target." doc: "The connection type for the target."
], ],
plug: [ plug: [
type: {:behaviour, Plug}, type: {:behaviour, Plug},

View file

@ -98,7 +98,7 @@ defmodule Wayfarer.Dsl.Transformer do
{target.scheme, target.module} {target.scheme, target.module}
target -> target ->
{target.scheme, IP.Address.to_tuple(target.address), target.port} {target.scheme, IP.Address.to_tuple(target.address), target.port, target.transport}
end) end)
end end
@ -113,7 +113,7 @@ defmodule Wayfarer.Dsl.Transformer do
|> Map.drop([:address, :health_checks, :port, :uri]) |> Map.drop([:address, :health_checks, :port, :uri])
|> Enum.to_list() |> Enum.to_list()
target when target.scheme in [:http, :https] -> target when target.scheme in [:http, :https, :ws, :wss] ->
health_checks = health_checks =
target.health_checks.health_checks target.health_checks.health_checks
|> Enum.map(&HealthCheck.to_options/1) |> Enum.map(&HealthCheck.to_options/1)

View file

@ -3,7 +3,7 @@ defmodule Wayfarer.Listener do
use GenServer, restart: :transient use GenServer, restart: :transient
require Logger require Logger
alias Spark.OptionsHelpers alias Spark.Options
import Wayfarer.Utils import Wayfarer.Utils
@options_schema [ @options_schema [
@ -90,7 +90,7 @@ defmodule Wayfarer.Listener do
## Options ## Options
#{OptionsHelpers.docs(@options_schema)} #{Options.docs(@options_schema)}
""" """
@doc false @doc false
@ -126,14 +126,14 @@ defmodule Wayfarer.Listener do
{:ok, :https} -> {:ok, :https} ->
schema = schema =
@options_schema @options_schema
|> OptionsHelpers.make_required!(:keyfile) |> Options.Helpers.make_required!(:keyfile)
|> OptionsHelpers.make_required!(:certfile) |> Options.Helpers.make_required!(:certfile)
|> OptionsHelpers.make_required!(:cipher_suite) |> Options.Helpers.make_required!(:cipher_suite)
OptionsHelpers.validate(options, schema) Options.validate(options, schema)
_ -> _ ->
OptionsHelpers.validate(options, @options_schema) Options.validate(options, @options_schema)
end end
end end

View file

@ -51,7 +51,17 @@ defmodule Wayfarer.Router do
local `Plug`. local `Plug`.
""" """
@type target :: @type target ::
{scheme, :inet.ip_address(), :socket.port_number()} {:http | :https | :ws | :wss, :inet.ip_address(), :socket.port_number(),
:http1 | :http2 | :auto}
| {:plug, module}
| {:plug, {module, any}}
@typedoc """
Like `t:target/0` except that it can contain user input for the address portion.
"""
@type target_input ::
{:http | :https | :ws | :wss, Wayfarer.Utils.address_input(), :socket.port_number(),
:http1 | :http2 | :auto}
| {:plug, module} | {:plug, module}
| {:plug, {module, any}} | {:plug, {module, any}}
@ -85,7 +95,8 @@ defmodule Wayfarer.Router do
This should only ever be called by `Wayfarer.Server` directly. This should only ever be called by `Wayfarer.Server` directly.
""" """
@spec add_route(:ets.tid(), listener, target, [host_name], algorithm) :: :ok | {:error, any} @spec add_route(:ets.tid(), listener, target_input, [host_name], algorithm) ::
:ok | {:error, any}
def add_route(table, listener, target, host_names, algorithm) do def add_route(table, listener, target, host_names, algorithm) do
with {:ok, entries} <- route_to_entries(table, listener, target, host_names, algorithm) do with {:ok, entries} <- route_to_entries(table, listener, target, host_names, algorithm) do
:ets.insert(table, entries) :ets.insert(table, entries)
@ -96,9 +107,9 @@ defmodule Wayfarer.Router do
end end
@doc """ @doc """
Add a number of router into the routing table. Add a number of routes into the routing table.
""" """
@spec import_routes(:ets.tid(), [{listener, target, [host_name], algorithm}]) :: :ok @spec import_routes(:ets.tid(), [{listener, target_input, [host_name], algorithm}]) :: :ok
def import_routes(table, routes) do def import_routes(table, routes) do
with {:ok, entries} <- routes_to_entries(table, routes) do with {:ok, entries} <- routes_to_entries(table, routes) do
:ets.insert(table, entries) :ets.insert(table, entries)
@ -142,15 +153,15 @@ defmodule Wayfarer.Router do
Change a target's health state. Change a target's health state.
""" """
@spec update_target_health_status(:ets.tid(), target, health) :: :ok @spec update_target_health_status(:ets.tid(), target, health) :: :ok
def update_target_health_status(table, {scheme, address, port}, status) do def update_target_health_status(table, {scheme, address, port, transport}, status) do
# Match spec generated using: # Match spec generated using:
# :ets.fun2ms(fn {listener, host_pattern, {:http, {192, 168, 4, 26}, 80}, algorithm, _} -> # :ets.fun2ms(fn {listener, host_pattern, {:http, {192, 168, 4, 26}, 80, transport}, algorithm, _} ->
# {listener, host_pattern, {:http, {192, 168, 4, 26}, 80}, algorithm, :healthy} # {listener, host_pattern, {:http, {192, 168, 4, 26}, 80, transport}, algorithm, :healthy}
# end) # end)
match_spec = [ match_spec = [
{{:"$1", :"$2", {scheme, address, port}, :"$3", :_}, [], {{:"$1", :"$2", {scheme, address, port, transport}, :"$3", :_}, [],
[{{:"$1", :"$2", {{scheme, {address}, port}}, :"$3", status}}]} [{{:"$1", :"$2", {{scheme, {address}, port, transport}}, :"$3", status}}]}
] ]
:ets.select_replace(table, match_spec) :ets.select_replace(table, match_spec)
@ -223,12 +234,12 @@ defmodule Wayfarer.Router do
message: "Value `#{inspect(algorithm)}` is not a valid load balancing algorithm." message: "Value `#{inspect(algorithm)}` is not a valid load balancing algorithm."
)} )}
defp current_health_state(table, {scheme, address, port}) do defp current_health_state(table, {scheme, address, port, transport}) do
# Generated using # Generated using
# :ets.fun2ms(fn {_, _, :target, :_, health} -> health end) # :ets.fun2ms(fn {_, _, :target, :_, health} -> health end)
match_spec = [ match_spec = [
{{:_, :_, {scheme, address, port}, :_, :"$1"}, [], [:"$1"]} {{:_, :_, {scheme, address, port, transport}, :_, :"$1"}, [], [:"$1"]}
] ]
case :ets.select(table, match_spec, 1) do case :ets.select(table, match_spec, 1) do
@ -277,6 +288,14 @@ defmodule Wayfarer.Router do
defp sanitise_listener(listener), defp sanitise_listener(listener),
do: {:error, ArgumentError.exception(message: "Not a valid listener: `#{inspect(listener)}")} do: {:error, ArgumentError.exception(message: "Not a valid listener: `#{inspect(listener)}")}
defp sanitise_transport(transport) when transport in [:http1, :http2, :auto],
do: {:ok, transport}
defp sanitise_transport(transport),
do:
{:error,
ArgumentError.exception(message: "Not a valid target transport: `#{inspect(transport)}`")}
defp sanitise_target({:plug, module}), do: sanitise_target({:plug, module, []}) defp sanitise_target({:plug, module}), do: sanitise_target({:plug, module, []})
defp sanitise_target({:plug, module, _}) do defp sanitise_target({:plug, module, _}) do
@ -290,7 +309,14 @@ defmodule Wayfarer.Router do
end end
end end
defp sanitise_target({scheme, address, port}), do: sanitise_listener({scheme, address, port}) defp sanitise_target({scheme, address, port, transport}) do
with {:ok, scheme} <- sanitise_scheme(scheme),
{:ok, address} <- sanitise_ip_address(address),
{:ok, port} <- sanitise_port(port),
{:ok, transport} <- sanitise_transport(transport) do
{:ok, {scheme, address, port, transport}}
end
end
defp sanitise_target(target), defp sanitise_target(target),
do: {:error, ArgumentError.exception(message: "Not a valid target: `#{inspect(target)}")} do: {:error, ArgumentError.exception(message: "Not a valid target: `#{inspect(target)}")}

View file

@ -1,5 +1,5 @@
defmodule Wayfarer.Server do defmodule Wayfarer.Server do
alias Spark.OptionsHelpers alias Spark.Options
alias Wayfarer.{Dsl, Listener, Router, Server, Target} alias Wayfarer.{Dsl, Listener, Router, Server, Target}
use GenServer use GenServer
require Logger require Logger
@ -7,8 +7,9 @@ defmodule Wayfarer.Server do
@callback child_spec(keyword()) :: Supervisor.child_spec() @callback child_spec(keyword()) :: Supervisor.child_spec()
@callback start_link(keyword()) :: GenServer.on_start() @callback start_link(keyword()) :: GenServer.on_start()
@scheme_type {:in, [:http, :https]} @scheme_type {:in, [:http, :https, :ws, :wss]}
@port_type {:in, 0..0xFFFF} @port_type {:in, 0..0xFFFF}
@transport_type {:in, [:http1, :http2, :auto]}
@ip_type {:or, @ip_type {:or,
[ [
{:tuple, [{:in, 0..0xFF}, {:in, 0..0xFF}, {:in, 0..0xFF}, {:in, 0..0xFF}]}, {:tuple, [{:in, 0..0xFF}, {:in, 0..0xFF}, {:in, 0..0xFF}, {:in, 0..0xFF}]},
@ -50,7 +51,7 @@ defmodule Wayfarer.Server do
{:tuple, {:tuple,
[ [
{:tuple, [@scheme_type, @ip_type, @port_type]}, {:tuple, [@scheme_type, @ip_type, @port_type]},
{:tuple, [@scheme_type, @ip_type, @port_type]}, {:tuple, [@scheme_type, @ip_type, @port_type, @transport_type]},
{:list, :string}, {:list, :string},
{:in, [:round_robin, :sticky, :random, :least_connections]} {:in, [:round_robin, :sticky, :random, :least_connections]}
]}}, ]}},
@ -78,7 +79,7 @@ defmodule Wayfarer.Server do
## Options ## Options
#{OptionsHelpers.docs(@options_schema)} #{Options.docs(@options_schema)}
""" """
@type options :: keyword @type options :: keyword
@ -117,13 +118,14 @@ defmodule Wayfarer.Server do
@doc false @doc false
@spec target_status_change( @spec target_status_change(
{module, :http | :https, IP.Address.t(), :socket.port_number()}, {module, :http | :https, IP.Address.t(), :socket.port_number(),
:http1 | :http2 | :auto},
Router.health() Router.health()
) :: :ok ) :: :ok
def target_status_change({module, scheme, address, port}, status) do def target_status_change({module, scheme, address, port, transport}, status) do
GenServer.cast( GenServer.cast(
{:via, Registry, {Wayfarer.Server.Registry, module}}, {:via, Registry, {Wayfarer.Server.Registry, module}},
{:target_status_change, scheme, address, port, status} {:target_status_change, scheme, address, port, transport, status}
) )
end end
@ -145,7 +147,7 @@ defmodule Wayfarer.Server do
@impl true @impl true
@spec init(options) :: {:ok, map} | {:stop, any} @spec init(options) :: {:ok, map} | {:stop, any}
def init(options) do def init(options) do
with {:ok, options} <- OptionsHelpers.validate(options, @options_schema), with {:ok, options} <- Options.validate(options, @options_schema),
{:ok, module} <- assert_is_server(options[:module]), {:ok, module} <- assert_is_server(options[:module]),
listeners <- Keyword.get(options, :listeners, []), listeners <- Keyword.get(options, :listeners, []),
targets <- Keyword.get(options, :targets, []), targets <- Keyword.get(options, :targets, []),
@ -165,10 +167,10 @@ defmodule Wayfarer.Server do
@doc false @doc false
@impl true @impl true
@spec handle_cast(any, map) :: {:noreply, map} @spec handle_cast(any, map) :: {:noreply, map}
def handle_cast({:target_status_change, scheme, address, port, status}, state) do def handle_cast({:target_status_change, scheme, address, port, transport, status}, state) do
Router.update_target_health_status( Router.update_target_health_status(
state.routing_table, state.routing_table,
{scheme, IP.Address.to_tuple(address), port}, {scheme, IP.Address.to_tuple(address), port, transport},
status status
) )

View file

@ -3,7 +3,7 @@ defmodule Wayfarer.Server.Plug do
Plug pipeline to handle inbound HTTP connections. Plug pipeline to handle inbound HTTP connections.
""" """
alias Wayfarer.{Router, Server.Proxy, Target.Selector} alias Wayfarer.{Router, Server.Proxy, Target.Selector, Telemetry}
require Logger require Logger
import Plug.Conn import Plug.Conn
@ -17,22 +17,57 @@ defmodule Wayfarer.Server.Plug do
@doc false @doc false
@impl true @impl true
@spec call(Plug.Conn.t(), map) :: Plug.Conn.t() @spec call(Plug.Conn.t(), map) :: Plug.Conn.t()
def call(conn, config) when is_atom(config.module) do def call(conn, config) do
conn = put_private(conn, :wayfarer, %{listener: config}) transport = get_transport(conn)
conn
|> put_private(:wayfarer, %{listener: config, transport: transport})
|> Telemetry.request_start()
|> do_call(config)
end
defp do_call(conn, config) when is_atom(config.module) do
listener = {config.scheme, config.address, config.port} listener = {config.scheme, config.address, config.port}
with {:ok, targets} <- Router.find_healthy_targets(config.module, listener, conn.host), with {:ok, targets} <- Router.find_healthy_targets(config.module, listener, conn.host),
{:ok, targets, algorithm} <- split_targets_and_algorithms(targets), {:ok, targets, algorithm} <- split_targets_and_algorithms(targets),
{:ok, target} <- Selector.choose(conn, targets, algorithm) do {:ok, target} <- Selector.choose(conn, targets, algorithm) do
do_proxy(conn, target) conn
|> Telemetry.request_routed(target, algorithm)
|> do_proxy(target)
else else
:error -> bad_gateway(conn) :error ->
{:error, reason} -> internal_error(conn, reason) conn
|> Telemetry.request_exception(:error, :target_not_found)
|> bad_gateway()
{:error, reason} ->
conn
|> Telemetry.request_exception(:error, reason)
|> internal_error(reason)
end end
rescue
exception ->
conn
|> Telemetry.request_exception(:exception, exception, __STACKTRACE__)
|> internal_error(exception)
catch
reason ->
conn
|> Telemetry.request_exception(:throw, reason)
|> internal_error(reason)
kind, reason ->
conn
|> Telemetry.request_exception(kind, reason)
|> internal_error(reason)
end end
def call(conn, _config), do: bad_gateway(conn) defp do_call(conn, _config) do
conn
|> Telemetry.request_exception(:error, :unrecognised_request)
|> bad_gateway()
end
defp internal_error(conn, reason) do defp internal_error(conn, reason) do
Logger.error("Internal error when routing proxy request: #{inspect(reason)}") Logger.error("Internal error when routing proxy request: #{inspect(reason)}")
@ -64,4 +99,12 @@ defmodule Wayfarer.Server.Plug do
do: split_targets_and_algorithms(tail, [target | targets], algorithm) do: split_targets_and_algorithms(tail, [target | targets], algorithm)
defp split_targets_and_algorithms([], targets, algorithm), do: {:ok, targets, algorithm} defp split_targets_and_algorithms([], targets, algorithm), do: {:ok, targets, algorithm}
defp get_transport(%{adapter: {Bandit.Adapter, %{transport: %Bandit.HTTP1.Socket{}}}}),
do: :http1
defp get_transport(%{adapter: {Bandit.Adapter, %{transport: %Bandit.HTTP2.Stream{}}}}),
do: :http2
defp get_transport(_), do: :unknown
end end

View file

@ -4,9 +4,9 @@ defmodule Wayfarer.Server.Proxy do
specific target. specific target.
""" """
alias Mint.HTTP alias Mint.{HTTP, HTTP1, HTTP2}
alias Plug.Conn alias Plug.Conn
alias Wayfarer.{Router, Target.ActiveConnections, Target.TotalConnections} alias Wayfarer.{Router, Target.ActiveConnections, Target.TotalConnections, Telemetry}
require Logger require Logger
@connect_timeout 5_000 @connect_timeout 5_000
@ -16,27 +16,67 @@ defmodule Wayfarer.Server.Proxy do
Convert the request conn into an HTTP request to the specified target. Convert the request conn into an HTTP request to the specified target.
""" """
@spec request(Conn.t(), Router.target()) :: Conn.t() @spec request(Conn.t(), Router.target()) :: Conn.t()
def request(conn, {scheme, address, port} = target) do def request(conn, target) do
with {:ok, mint} <- with {:ok, mint} <- connect(conn, target),
HTTP.connect(scheme, address, port, hostname: conn.host, timeout: @connect_timeout),
:ok <- ActiveConnections.connect(target), :ok <- ActiveConnections.connect(target),
:ok <- TotalConnections.proxy_connect(target), :ok <- TotalConnections.proxy_connect(target) do
{:ok, body, conn} <- read_request_body(conn), handle_request(mint, conn, target)
{:ok, mint, req} <- send_request(conn, mint, body),
{:ok, conn, _mint} <- proxy_responses(conn, mint, req) do
conn
else else
error -> handle_error(error, conn, target) error ->
conn
|> Telemetry.request_exception(:error, error)
|> handle_error(error, target)
end end
end end
defp handle_error({:error, _, reason}, conn, target), defp handle_request(mint, conn, {proto, _, _, _}) do
do: handle_error({:error, reason}, conn, target) if http1?(conn) && connection_wants_upgrade?(conn) && upgrade_is_websocket?(conn) do
handle_websocket_request(mint, conn, proto)
else
handle_http_request(mint, conn)
end
end
defp handle_error({:error, reason}, conn, {scheme, address, port}) do defp handle_http_request(mint, conn) do
Logger.error( with {:ok, mint, req} <- send_request(conn, mint),
"Proxy error [phase=#{connection_phase(conn)},ip=#{:inet.ntoa(address)},port=#{port},proto=#{scheme}]: #{message(reason)}" {:ok, mint, conn} <- stream_request_body(conn, mint, req),
) {:ok, conn, _mint} <- proxy_responses(conn, mint, req) do
conn
|> Telemetry.request_stop()
end
end
defp handle_websocket_request(mint, conn, proto) do
conn
|> WebSockAdapter.upgrade(Wayfarer.Server.WebSocketProxy, {mint, conn, proto}, compress: true)
|> Telemetry.request_upgraded()
end
defp connect(conn, {:ws, address, port, transport}),
do: connect(conn, {:http, address, port, transport})
defp connect(conn, {:wss, address, port, transport}),
do: connect(conn, {:https, address, port, transport})
defp connect(conn, {scheme, address, port, :http1}) when is_tuple(address),
do: HTTP1.connect(scheme, address, port, hostname: conn.host, timeout: @connect_timeout)
defp connect(conn, {scheme, address, port, :http2}) when is_tuple(address),
do: HTTP2.connect(scheme, address, port, hostname: conn.host, timeout: @connect_timeout)
defp connect(conn, {scheme, address, port, :auto}) when is_tuple(address),
do: HTTP.connect(scheme, address, port, hostname: conn.host, timeout: @connect_timeout)
defp handle_error(conn, {:error, _, reason}, target),
do: handle_error(conn, {:error, reason}, target)
defp handle_error(conn, {:error, reason}, {scheme, address, port, transport}) do
Logger.error(fn ->
phase = connection_phase(conn)
ip = :inet.ntoa(address)
"Proxy error [phase=#{phase},ip=#{ip},port=#{port},proto=#{scheme},trans=#{transport}]: #{message(reason)}"
end)
if conn.halted || conn.state in [:sent, :chunked, :upgraded] do if conn.halted || conn.state in [:sent, :chunked, :upgraded] do
# Sadly there's not much more we can do here. # Sadly there's not much more we can do here.
@ -50,7 +90,54 @@ defmodule Wayfarer.Server.Proxy do
end end
end end
defp connection_phase(nil), do: "connect" defp http1?(%{private: %{wayfarer: %{transport: :http1}}}), do: true
defp http1?(_), do: false
defp connection_wants_upgrade?(conn) do
case Conn.get_req_header(conn, "connection") do
["Upgrade"] ->
true
["upgrade"] ->
true
[] ->
false
maybe ->
maybe
|> Enum.flat_map(&String.split(&1, ~r/,\s*/))
|> Enum.map(fn chunk ->
chunk
|> String.trim()
|> String.downcase()
end)
|> Enum.member?("upgrade")
end
end
defp upgrade_is_websocket?(conn) do
case Conn.get_req_header(conn, "upgrade") do
["websocket"] ->
true
[] ->
false
maybe ->
maybe
|> Enum.flat_map(&String.split(&1, ~r/,\s*/))
|> Enum.map(fn chunk ->
chunk
|> String.trim()
|> String.split("/")
|> hd()
|> String.downcase()
end)
|> Enum.member?("websocket")
end
end
defp connection_phase(conn) when conn.state in [:chunked, :upgraded], do: "stream" defp connection_phase(conn) when conn.state in [:chunked, :upgraded], do: "stream"
defp connection_phase(conn) when conn.halted, do: "done" defp connection_phase(conn) when conn.halted, do: "done"
defp connection_phase(conn) when conn.state == :sent, do: "done" defp connection_phase(conn) when conn.state == :sent, do: "done"
@ -72,92 +159,163 @@ defmodule Wayfarer.Server.Proxy do
receive do receive do
message -> message ->
case HTTP.stream(mint, message) do case HTTP.stream(mint, message) do
:unknown -> proxy_responses(conn, mint, req) :unknown ->
{:ok, mint, responses} -> handle_responses(responses, conn, mint, req) proxy_responses(conn, mint, req)
{:error, _, reason, _} -> {:error, reason}
{:ok, mint, responses} ->
handle_responses(conn, responses, mint, req)
{:error, _, reason, _} ->
{:error, reason}
end end
after after
@idle_timeout -> {:error, conn, :idle_timeout} @idle_timeout -> {:error, :idle_timeout}
end end
end end
defp handle_responses([], conn, mint, req), do: proxy_responses(conn, mint, req) defp handle_responses(conn, [], mint, req), do: proxy_responses(conn, mint, req)
defp handle_responses([{:status, req, status} | responses], conn, mint, req), defp handle_responses(conn, [{:status, req, status} | responses], mint, req) do
do: handle_responses(responses, Conn.put_status(conn, status), mint, req) conn
|> Conn.put_status(status)
defp handle_responses([{:headers, req, headers} | responses], conn, mint, req) do |> Telemetry.request_received_status(status)
conn = |> handle_responses(responses, mint, req)
headers
|> Enum.reduce(conn, &Conn.put_resp_header(&2, elem(&1, 0), elem(&1, 1)))
handle_responses(responses, conn, mint, req)
end end
defp handle_responses([{:data, req, body} | responses], conn, mint, req) defp handle_responses(conn, [{:headers, req, headers} | responses], mint, req) do
headers
|> Enum.reduce(conn, fn {header_name, header_value}, conn ->
conn
|> Conn.put_resp_header(header_name, header_value)
end)
|> handle_responses(responses, mint, req)
end
defp handle_responses(conn, [{:data, req, body} | responses], mint, req)
when conn.state == :chunked do when conn.state == :chunked do
case Conn.chunk(conn, body) do case Conn.chunk(conn, body) do
{:ok, conn} -> handle_responses(responses, conn, mint, req) {:ok, conn} ->
{:error, reason} -> {:error, conn, reason} body_size = byte_size(body)
conn
|> Telemetry.increment_metrics(%{resp_body_bytes: body_size})
|> Telemetry.request_resp_body_chunk(body_size)
|> handle_responses(responses, mint, req)
{:error, reason} ->
{:error, conn, reason}
end end
end end
defp handle_responses([{:data, req, body} | responses], conn, mint, req) do defp handle_responses(conn, [{:data, req, body} | responses], mint, req) do
# We need to check here for a content-length or transfer encoding header and # We need to check here for a content-length or transfer encoding header and
# deal with it. This should be refactored out into a proxy state rather # deal with it. This should be refactored out into a proxy state rather
# than using the conn as our state. # than using the conn as our state.
body_size = byte_size(body)
case Conn.get_resp_header(conn, "content-length") do case Conn.get_resp_header(conn, "content-length") do
[] -> [] ->
conn = Conn.send_chunked(conn, conn.status) conn
handle_responses([{:data, req, body} | responses], conn, mint, req) |> Conn.send_chunked(conn.status)
|> Telemetry.request_resp_started()
|> handle_responses([{:data, req, body} | responses], mint, req)
[length] -> [length] ->
if String.to_integer(length) == byte_size(body) do if String.to_integer(length) == body_size do
conn = conn =
conn conn
|> Conn.delete_resp_header("content-length")
|> Conn.send_resp(conn.status, body) |> Conn.send_resp(conn.status, body)
|> Telemetry.request_resp_started()
|> Telemetry.increment_metrics(%{resp_body_bytes: body_size})
|> Telemetry.request_resp_body_chunk(body_size)
|> Conn.halt() |> Conn.halt()
{:ok, conn, mint} {:ok, conn, mint}
else else
conn = conn =
conn conn
|> Conn.delete_resp_header("content-length") |> Telemetry.increment_metrics(%{resp_body_bytes: body_size})
|> Telemetry.request_resp_body_chunk(body_size)
|> Conn.send_chunked(conn.status) |> Conn.send_chunked(conn.status)
handle_responses([{:data, req, body} | responses], conn, mint, req) handle_responses(conn, [{:data, req, body} | responses], mint, req)
end end
end end
end end
defp handle_responses([{:done, req} | _], conn, mint, req) do # If the connection is done without sending any body content, then we need to
{:ok, Conn.halt(conn), mint} # send the respond and halt the conn.
defp handle_responses(conn, [{:done, req} | _], mint, req) when conn.state == :unset do
conn =
conn
|> Conn.send_resp(conn.status, "")
|> Conn.halt()
{:ok, conn, mint}
end end
defp handle_responses([{:error, req, reason} | _], conn, _mint, req), do: {:error, conn, reason} defp handle_responses(conn, [{:done, req} | _], mint, req) when conn.state == :chunked,
do: {:ok, Conn.halt(conn), mint}
# This is bad - we need to figure out how to stream the body, but it's fine defp handle_responses(conn, [{:error, req, reason} | _], _mint, req), do: {:error, conn, reason}
# for now.
defp read_request_body(conn, body \\ <<>>) do defp send_request(conn, mint) do
case Conn.read_body(conn) do request_path =
{:ok, chunk, conn} -> {:ok, body <> chunk, conn} case {conn.request_path, conn.query_string} do
{:more, chunk, conn} -> read_request_body(conn, body <> chunk) {path, nil} -> path
{:error, reason} -> {:error, conn, reason} {path, ""} -> path
end {path, query} -> path <> "?" <> query
end end
defp send_request(conn, mint, body) do
HTTP.request( HTTP.request(
mint, mint,
conn.method, conn.method,
conn.request_path, request_path,
proxy_headers(conn), proxy_headers(conn),
body :stream
) )
end end
defp stream_request_body(conn, mint, req) do
case Conn.read_body(conn) do
{:ok, <<>>, conn} ->
with {:ok, mint} <- HTTP.stream_request_body(mint, req, :eof) do
conn =
conn
|> Telemetry.set_metrics(%{req_body_bytes: 0, req_body_chunks: 0})
{:ok, mint, conn}
end
{:ok, chunk, conn} ->
with {:ok, mint} <- HTTP.stream_request_body(mint, req, chunk),
{:ok, mint} <- HTTP.stream_request_body(mint, req, :eof) do
chunk_size = byte_size(chunk)
conn =
conn
|> Telemetry.increment_metrics(%{req_body_bytes: chunk_size, req_body_chunks: 1})
|> Telemetry.request_req_body_chunk(chunk_size)
{:ok, mint, conn}
end
{:more, chunk, conn} ->
with {:ok, mint} <- HTTP.stream_request_body(mint, req, chunk) do
chunk_size = byte_size(chunk)
conn
|> Telemetry.increment_metrics(%{req_body_bytes: chunk_size, req_body_chunks: 1})
|> Telemetry.request_req_body_chunk(chunk_size)
|> stream_request_body(mint, req)
end
{:error, reason} ->
{:error, conn, reason}
end
end
defp proxy_headers(conn) do defp proxy_headers(conn) do
listener = listener =
conn.private.wayfarer.listener conn.private.wayfarer.listener

View file

@ -0,0 +1,256 @@
defmodule Wayfarer.Server.WebSocketProxy do
@moduledoc """
When a connection is upgraded to a websocket, we switch from handing via
`Plug` to `WebSock` via `WebSockAdapter`.
The outgoing connection is made using `Mint.WebSocket`.
"""
@behaviour WebSock
alias Mint.WebSocket
alias Plug.Conn
alias Wayfarer.Telemetry
require Logger
@default_opts [extensions: [WebSocket.PerMessageDeflate]]
@doc false
@impl true
def init({mint, conn, proto}) when proto in [:ws, :wss] do
request_path =
case {conn.request_path, conn.query_string} do
{path, nil} -> path
{path, ""} -> path
{path, query} -> path <> "?" <> query
end
case WebSocket.upgrade(proto, mint, request_path, proxy_headers(conn), @default_opts) do
{:ok, mint, ref} -> {:ok, %{mint: mint, ref: ref, status: :init, buffer: [], conn: conn}}
{:error, _mint, reason} -> {:error, reason}
end
end
def init({mint, conn, :https}), do: init({mint, conn, :wss})
def init({mint, conn, :http}), do: init({mint, conn, :ws})
@doc false
@impl true
def handle_control({frame, [{:opcode, :ping}]}, state) do
with {:ok, websocket, data} <- WebSocket.encode(state.websocket, {:ping, frame}),
{:ok, mint} <- WebSocket.stream_request_body(state.mint, state.ref, data) do
conn = request_client_frame(state.conn, {:ping, frame})
{:ok, %{state | websocket: websocket, mint: mint, conn: conn}}
else
error -> handle_error(error, state)
end
end
def handle_control({frame, [{:opcode, frame_type}]}, state) do
conn = request_client_frame(state.conn, {frame_type, frame})
{:ok, %{state | conn: conn}}
end
@doc false
@impl true
def handle_in({payload, [{:opcode, frame_type}]}, state) when state.status == :init do
frame = {frame_type, payload}
buffer = [frame | state.buffer]
conn = request_client_frame(state.conn, frame)
{:ok, %{state | buffer: buffer, conn: conn}}
end
def handle_in({payload, [{:opcode, frame_type}]}, state) do
with {:ok, websocket, data} <- WebSocket.encode(state.websocket, {frame_type, payload}),
{:ok, mint} <- WebSocket.stream_request_body(state.mint, state.ref, data) do
conn = request_client_frame(state.conn, {frame_type, payload})
{:ok, %{state | websocket: websocket, mint: mint, conn: conn}}
else
error -> handle_error(error, state)
end
end
@doc false
@impl true
def handle_info(msg, state) when state.status == :init do
with {:ok, mint, result} <- WebSocket.stream(state.mint, msg),
{:ok, result} <- handle_upgrade_response(result, state.ref),
{:ok, mint, websocket} <- WebSocket.new(mint, state.ref, result.status, result.headers),
state <- Map.merge(state, %{status: :connected, websocket: websocket, mint: mint}),
{:ok, state} <- empty_buffer(state),
{:ok, messages, state} <- decode_frames(result.data, state) do
response_for_messages(messages, state)
else
error -> handle_error(error, state)
end
end
def handle_info(msg, state) when state.status == :connected do
with {:ok, mint, result} <- WebSocket.stream(state.mint, msg),
{:ok, frames} <- handle_websocket_data(result, state.ref),
{:ok, messages, state} <- decode_frames(frames, %{state | mint: mint}) do
response_for_messages(messages, state)
else
error -> handle_error(error, state)
end
end
@doc false
@impl true
def terminate(_reason, _state), do: :ok
defp handle_error({:error, _, %{reason: reason}, _}, state),
do: handle_error({:error, reason}, state)
defp handle_error({:error, reason, state}, _state),
do: handle_error({:error, reason}, state)
defp handle_error({:error, reason}, state) do
Logger.debug(fn ->
"Dropping WebSocket connection for reason: #{inspect(reason)}"
end)
{:stop, :normal, state}
end
defp proxy_headers(conn) do
listener =
conn.private.wayfarer.listener
|> case do
%{address: address, port: port} when tuple_size(address) == 8 ->
"[#{:inet.ntoa(address)}]:#{port}"
%{address: address, port: port} ->
"#{:inet.ntoa(address)}:#{port}"
end
client =
conn
|> Conn.get_peer_data()
|> case do
%{address: address, port: port} when tuple_size(address) == 8 ->
"[#{:inet.ntoa(address)}]:#{port}"
%{address: address, port: port} ->
"#{:inet.ntoa(address)}:#{port}"
end
req_headers =
conn.req_headers
|> Enum.reject(
&(elem(&1, 0) in [
"connection",
"upgrade",
"sec-websocket-extensions",
"sec-websocket-key",
"sec-websocket-version"
])
)
[
{"forwarded", "by=#{listener};for=#{client};host=#{conn.host};proto=#{conn.scheme}"}
| req_headers
]
end
defp empty_buffer(state) when state.buffer == [], do: {:ok, state}
defp empty_buffer(state), do: do_empty_buffer(Enum.reverse(state.buffer), %{state | buffer: []})
defp do_empty_buffer([], state), do: {:ok, state}
defp do_empty_buffer([head | tail], state) do
with {:ok, websocket, data} <- WebSocket.encode(state.websocket, head),
{:ok, mint} <- WebSocket.stream_request_body(state.mint, state.ref, data) do
conn = request_client_frame(state.conn, head)
do_empty_buffer(tail, %{state | websocket: websocket, mint: mint, conn: conn})
end
end
defp handle_upgrade_response(result, ref), do: handle_upgrade_response(result, ref, %{data: []})
defp handle_upgrade_response([{:done, ref}], ref, result), do: {:ok, result}
defp handle_upgrade_response([{:status, ref, status} | tail], ref, result) do
handle_upgrade_response(tail, ref, Map.put(result, :status, status))
end
defp handle_upgrade_response([{:headers, ref, headers} | tail], ref, result) do
handle_upgrade_response(tail, ref, Map.put(result, :headers, headers))
end
defp handle_upgrade_response([{:data, ref, data} | tail], ref, result) do
result = Map.update!(result, :data, &[data | &1])
handle_upgrade_response(tail, ref, result)
end
defp handle_websocket_data(data, ref),
do: handle_websocket_data(data, ref, [])
defp handle_websocket_data([], _ref, messages), do: {:ok, Enum.reverse(messages)}
defp handle_websocket_data([{:data, ref, data} | tail], ref, messages),
do: handle_websocket_data(tail, ref, [data | messages])
defp decode_frames(frames, state) do
frames
|> Enum.reduce_while({:ok, [], state}, fn frame, {:ok, messages, state} ->
case decode_frame(state, frame) do
{:ok, new_messages, state} -> {:cont, {:ok, [new_messages, messages], state}}
{:error, reason, state} -> {:halt, {:error, reason, state}}
end
end)
|> case do
{:ok, messages, state} -> {:ok, List.flatten(messages), state}
{:error, reason, state} -> {:error, reason, state}
end
end
defp decode_frame(state, frame) do
case WebSocket.decode(state.websocket, frame) do
{:ok, websocket, frames} when is_list(frames) ->
conn = Enum.reduce(frames, state.conn, &request_server_frame(&2, &1))
{:ok, frames, %{state | websocket: websocket, conn: conn}}
{:error, websocket, reason} ->
{:error, reason, %{state | websocket: websocket}}
end
end
# Handle all the frames coming from the target and decide how to respond to
# Bandit/WebSock. In the case of encountering a close frame, we terminate the
# client websocket with the same code, otherwise we just copy the frames over.
defp response_for_messages(messages, state, response \\ [])
defp response_for_messages([], state, []), do: {:ok, state}
defp response_for_messages([], state, response), do: {:push, Enum.reverse(response), state}
defp response_for_messages([{:close, code, _} | _], state, response),
do: {:stop, :normal, code, Enum.reverse(response), state}
defp response_for_messages([message | messages], state, response),
do: response_for_messages(messages, state, [message | response])
defp request_client_frame(conn, {frame_type, frame}) do
frame_size = byte_size(frame)
conn
|> Telemetry.increment_metrics(%{
client_frame_bytes: frame_size,
client_frame_count: 1
})
|> Telemetry.request_client_frame(frame_size, frame_type)
end
defp request_server_frame(conn, {frame_type, frame}) do
frame_size = byte_size(frame)
conn
|> Telemetry.increment_metrics(%{
server_frame_bytes: frame_size,
server_frame_count: 1
})
|> Telemetry.request_server_frame(frame_size, frame_type)
end
end

View file

@ -3,14 +3,14 @@ defmodule Wayfarer.Target do
use GenServer, restart: :transient use GenServer, restart: :transient
require Logger require Logger
alias Spark.OptionsHelpers alias Spark.Options
alias Wayfarer.{Dsl.HealthCheck, Router, Server, Target} alias Wayfarer.{Dsl.HealthCheck, Router, Server, Target}
import Wayfarer.Utils import Wayfarer.Utils
@options_schema [ @options_schema [
scheme: [ scheme: [
type: {:in, [:http, :https]}, type: {:in, [:http, :https, :ws, :wss]},
doc: "The connection protocol.", doc: "The connection scheme.",
required: true required: true
], ],
port: [ port: [
@ -33,6 +33,12 @@ defmodule Wayfarer.Target do
doc: "An optional name for the target.", doc: "An optional name for the target.",
required: false required: false
], ],
transport: [
type: {:in, [:http1, :http2, :auto]},
required: false,
default: :auto,
doc: "The connection protocol."
],
health_checks: [ health_checks: [
type: {:list, {:keyword_list, HealthCheck.schema()}}, type: {:list, {:keyword_list, HealthCheck.schema()}},
required: false, required: false,
@ -61,7 +67,7 @@ defmodule Wayfarer.Target do
## Options ## Options
#{OptionsHelpers.docs(@options_schema)} #{Options.docs(@options_schema)}
""" """
@type key :: {module, :http | :https, IP.Address.t(), :socket.port_number()} @type key :: {module, :http | :https, IP.Address.t(), :socket.port_number()}
@ -90,9 +96,9 @@ defmodule Wayfarer.Target do
@doc false @doc false
@impl true @impl true
def init(options) do def init(options) do
with {:ok, options} <- OptionsHelpers.validate(options, @options_schema), with {:ok, options} <- Options.validate(options, @options_schema),
{:ok, uri} <- to_uri(options[:scheme], options[:address], options[:port]) do {:ok, uri} <- to_uri(options[:scheme], options[:address], options[:port]) do
target = options |> Keyword.take(~w[scheme address port]a) |> Map.new() target = options |> Keyword.take(~w[scheme address port transport]a) |> Map.new()
module = options[:module] module = options[:module]
key = {module, target.scheme, target.address, target.port} key = {module, target.scheme, target.address, target.port}
@ -117,6 +123,7 @@ defmodule Wayfarer.Target do
method: check[:method] |> to_string() |> String.upcase(), method: check[:method] |> to_string() |> String.upcase(),
headers: @default_headers, headers: @default_headers,
hostname: check[:hostname] || uri.host, hostname: check[:hostname] || uri.host,
transport: target.transport,
passes: 0 passes: 0
}) })
@ -170,7 +177,8 @@ defmodule Wayfarer.Target do
end) end)
Server.target_status_change( Server.target_status_change(
{state.module, state.target.scheme, state.target.address, state.target.port}, {state.module, state.target.scheme, state.target.address, state.target.port,
state.target.transport},
:unhealthy :unhealthy
) )
@ -205,7 +213,8 @@ defmodule Wayfarer.Target do
if target_became_healthy? do if target_became_healthy? do
Server.target_status_change( Server.target_status_change(
{state.module, state.target.scheme, state.target.address, state.target.port}, {state.module, state.target.scheme, state.target.address, state.target.port,
state.target.transport},
:healthy :healthy
) )

View file

@ -36,8 +36,8 @@ defmodule Wayfarer.Target.ActiveConnections do
@impl true @impl true
@spec handle_info(:tick | {:DOWN, any, :process, any, pid, any}, state) :: {:noreply, state} @spec handle_info(:tick | {:DOWN, any, :process, any, pid, any}, state) :: {:noreply, state}
def handle_info(:tick, state) do def handle_info(:tick, state) do
size = :ets.info(state.table, :size) # size = :ets.info(state.table, :size)
Logger.debug("Active connections: #{size}") # Logger.debug("Active connections: #{size}")
{:noreply, state} {:noreply, state}
end end

View file

@ -4,23 +4,48 @@ defmodule Wayfarer.Target.Check do
""" """
use GenServer, restart: :transient use GenServer, restart: :transient
alias Mint.HTTP alias Mint.{HTTP, HTTP1, HTTP2, WebSocket}
alias Wayfarer.{Target, Target.TotalConnections} alias Wayfarer.{Target, Target.TotalConnections, Telemetry}
require Logger require Logger
@type state :: %{
conn: struct(),
req: reference(),
scheme: :http | :https | :ws | :wss,
address: :inet.ip_address(),
port: :socket.port_number(),
uri: URI.t(),
ref: any,
method: String.t(),
headers: [{String.t(), String.t()}],
hostname: String.t(),
transport: :http1 | :http2 | :auto,
span: map
}
@doc false @doc false
@impl true @impl true
def init(state), do: {:ok, state, {:continue, :start_check}} def init(state), do: {:ok, state, {:continue, :start_check}}
@doc false @doc false
@impl true @impl true
@spec handle_continue(:start_check, state) :: {:noreply, state, timeout} | {:stop, :normal, nil}
def handle_continue(:start_check, state) do def handle_continue(:start_check, state) do
with {:ok, conn} <- connect(state), state =
{:ok, conn, req} <- request(Map.put(state, :conn, conn)) do state
state = |> Map.put(:span, %{
state metadata: %{
|> Map.merge(%{conn: conn, req: req}) target: %{scheme: state.scheme, address: state.address, port: state.port},
method: state.method,
uri: state.uri,
hostname: state.hostname,
telemetry_span_context: make_ref()
}
})
|> Telemetry.health_check_start()
with {:ok, state} <- connect(state),
{:ok, state} <- request(state) do
{:noreply, state, state.response_timeout} {:noreply, state, state.response_timeout}
else else
{:error, reason} -> {:error, reason} ->
@ -33,59 +58,121 @@ defmodule Wayfarer.Target.Check do
@doc false @doc false
@impl true @impl true
def handle_info(:timeout, state), do: check_failed(state, "request timeout expired.") def handle_info(:timeout, state), do: check_failed(state, :timeout)
def handle_info(message, state) do def handle_info(message, state) do
with {:ok, conn, responses} <- Mint.HTTP.stream(state.conn, message), with {:ok, conn, responses} <- WebSocket.stream(state.conn, message),
:ok <- TotalConnections.health_check_connect({state.scheme, state.address, state.port}), :ok <-
TotalConnections.health_check_connect(
{state.scheme, state.address, state.port, state.transport}
),
{:ok, status} <- get_status_response(conn, responses) do {:ok, status} <- get_status_response(conn, responses) do
if Enum.any?(state.success_codes, &Enum.member?(&1, status)) do if Enum.any?(state.success_codes, &Enum.member?(&1, status)) do
Target.check_passed(state.ref) Target.check_passed(state.ref)
Telemetry.health_check_pass(state, status)
{:stop, :normal, nil} {:stop, :normal, nil}
else else
check_failed(state, "received #{status} status code") check_failed(state, "received #{status} status code")
end end
else else
{:continue, conn} -> {:continue, conn} ->
{:noreply, %{state | conn: conn}} {:noreply, Map.put(state, :conn, conn)}
:unknown -> :unknown ->
check_failed(state, "Received unknown message: `#{inspect(message)}`") check_failed(state, "Received unknown message: `#{inspect(message)}`")
{:error, reason} -> {:error, _conn, error, _responses} ->
check_failed(state, reason) check_failed(state, error)
end end
end end
defp connect(state), defp connect(state) when state.scheme == :ws,
do: do: connect(%{state | scheme: :http})
HTTP.connect(state.scheme, state.address, state.port,
timeout: state.connect_timeout,
hostname: state.hostname
)
defp request(state), defp connect(state) when state.scheme == :wss,
do: HTTP.request(state.conn, state.method, state.path, state.headers, nil) do: connect(%{state | scheme: :https})
defp connect(state) when state.transport == :http1 do
with {:ok, conn} <-
HTTP1.connect(state.scheme, state.address, state.port,
timeout: state.connect_timeout,
hostname: state.hostname
) do
{:ok, Telemetry.health_check_connect(Map.put(state, :conn, conn), :http1)}
end
end
defp connect(state) when state.transport == :http2 do
with {:ok, conn} <-
HTTP2.connect(state.scheme, state.address, state.port,
timeout: state.connect_timeout,
hostname: state.hostname
) do
{:ok, Telemetry.health_check_connect(Map.put(state, :conn, conn), :http2)}
end
end
defp connect(state) do
with {:ok, conn} <-
HTTP.connect(state.scheme, state.address, state.port,
timeout: state.connect_timeout,
hostname: state.hostname
) do
transport =
case conn do
%Mint.HTTP1{} -> :http1
%Mint.HTTP2{} -> :http2
end
{:ok, Telemetry.health_check_connect(Map.put(state, :conn, conn), transport)}
end
end
defp request(state) when state.scheme in [:ws, :wss] do
with {:ok, conn, req} <-
WebSocket.upgrade(state.scheme, state.conn, state.path, state.headers, []) do
state = Map.merge(state, %{conn: conn, req: req})
{:ok, Telemetry.health_check_request(state)}
end
end
defp request(state) do
with {:ok, conn, req} <-
HTTP.request(state.conn, state.method, state.path, state.headers, nil) do
state = Map.merge(state, %{conn: conn, req: req})
{:ok, Telemetry.health_check_request(state)}
end
end
defp check_failed(state, reason) when is_binary(reason) do defp check_failed(state, reason) when is_binary(reason) do
Target.check_failed(state.ref) Target.check_failed(state.ref)
Logger.warning("Health check failed for #{state.method} #{state.uri}: #{reason}.") Telemetry.health_check_fail(state, reason)
Logger.warning(fn -> "Health check failed for #{state.method} #{state.uri}: #{reason}." end)
{:stop, :normal, nil} {:stop, :normal, nil}
end end
defp check_failed(state, exception) when is_exception(exception) do defp check_failed(state, exception) when is_exception(exception) do
Target.check_failed(state.ref) Target.check_failed(state.ref)
Telemetry.health_check_fail(state, exception)
Logger.warning( Logger.warning(fn ->
"Health check failed for #{state.method} #{state.uri}: #{Exception.message(exception)}" "Health check failed for #{state.method} #{state.uri}: #{Exception.message(exception)}"
) end)
{:stop, :normal, nil} {:stop, :normal, nil}
end end
defp check_failed(state, reason) do defp check_failed(state, reason) do
Target.check_failed(state.ref) Target.check_failed(state.ref)
Logger.warning("Health check failed for #{state.method} #{state.uri}: `#{inspect(reason)}`") Telemetry.health_check_fail(state, reason)
Logger.warning(fn ->
"Health check failed for #{state.method} #{state.uri}: `#{inspect(reason)}`"
end)
{:stop, :normal, nil} {:stop, :normal, nil}
end end

764
lib/wayfarer/telemetry.ex Normal file
View file

@ -0,0 +1,764 @@
defmodule Wayfarer.Telemetry do
@moduledoc """
Wayfarer emits a number of telemetry events and spans on top of the excellent
telemetry events emitted by `Bandit.Telemetry`.
"""
alias Plug.Conn
alias Wayfarer.{Router, Target.Check, Target.Selector}
@typedoc """
Information about the target a request has been routed to.
"""
@type target ::
%{
scheme: :http | :https | :ws | :wss,
address: :inet.ip_address(),
port: :socket.port_number()
}
| %{scheme: :plug, module: module, options: any}
@typedoc """
Information about the listener that received the request.
"""
@type listener ::
%{
scheme: :http | :https,
module: module(),
port: :socket.port_number(),
address: :inet.ip_address()
}
@typedoc """
The time that the event was emitted, in `:native` time units.
This is sources from `System.monotonic_time/0` which has some caveats but in
general is better for calculating durations as it should never go backwards.
"""
@type monotonic_time :: integer()
@typedoc """
The time passed since the beginning of the span, in `:native` time units.
The difference between the current `monotonic_time` and the first
`monotonic_time` at the start of the span.
"""
@type duration :: integer()
@typedoc "The HTTP protocol version of the request"
@type transport :: :http1 | :http2
@typedoc "A unique identifier for the span"
@type telemetry_span_context :: reference()
@typedoc "A convenience type for describing telemetry events"
@opaque event(measurements, metadata) :: {measurements, metadata}
@typedoc """
The `[:wayfarer, :request, :start]` event.
This event signals the start of a request span tracking a client request to
completion.
You can use the `telemetry_span_context` metadata value to correlate
subsequent events within the same span.
"""
@type request_start ::
event(
%{
required(:monotonic_time) => monotonic_time(),
required(:wallclock_time) => DateTime.t()
},
%{
required(:conn) => Conn.t(),
required(:listener) => listener(),
required(:transport) => transport(),
required(:telemetry_span_context) => telemetry_span_context()
}
)
@typedoc """
The `[:wayfarer, :request, :routed]` event.
This event signals that the routing process has completed and a target has
been chosen to serve the request.
"""
@type request_routed ::
event(
%{
required(:monotonic_time) => monotonic_time(),
required(:duration) => duration(),
required(:wallclock_time) => DateTime.t()
},
%{
required(:conn) => Conn.t(),
required(:listener) => listener(),
required(:transport) => transport(),
required(:telemetry_span_context) => telemetry_span_context(),
required(:target) => target(),
required(:algorithm) => Selector.algorithm()
}
)
@typedoc """
The `[:wayfarer, :request, :exception]` event.
This event signals that something went wrong while processing the event. You
will likely still receive other events (eg `:stop`) for this span however.
"""
@type request_exception ::
event(
%{
required(:monotonic_time) => monotonic_time(),
required(:duration) => duration(),
required(:wallclock_time) => DateTime.t()
},
%{
required(:conn) => Conn.t(),
required(:listener) => listener(),
required(:transport) => transport(),
required(:telemetry_span_context) => telemetry_span_context(),
optional(:target) => target(),
optional(:algorithm) => Selector.algorithm(),
required(:kind) => :throw | :exit | :error | :exception,
required(:reason) => any,
required(:stacktrace) => Exception.stacktrace()
}
)
@typedoc """
The `[:wayfarer, :request, :stop]` event.
This event signals that the request has completed.
The measurements will contain any incrementing counters accumulated during the
course of the request.
"""
@type request_stop ::
event(
%{
required(:monotonic_time) => monotonic_time(),
required(:duration) => duration(),
required(:wallclock_time) => DateTime.t(),
required(:status) => nil | 100..599,
optional(:req_body_bytes) => non_neg_integer(),
optional(:resp_body_bytes) => non_neg_integer(),
optional(:client_frame_bytes) => non_neg_integer(),
optional(:client_frame_count) => non_neg_integer(),
optional(:server_frame_bytes) => non_neg_integer(),
optional(:server_frame_count) => non_neg_integer()
},
%{
required(:conn) => Conn.t(),
required(:listener) => listener(),
required(:transport) => transport(),
required(:telemetry_span_context) => telemetry_span_context(),
optional(:target) => target(),
optional(:algorithm) => Selector.algorithm(),
optional(:status) => nil | 100..599,
optional(:kind) => :throw | :exit | :error | :exception,
optional(:reason) => any,
optional(:stacktrace) => Exception.stacktrace()
}
)
@typedoc """
The `[:wayfarer, :request, :received_status]` event.
This event signals that an HTTP status code has been received from the
upstream target.
"""
@type request_received_status ::
event(
%{
required(:monotonic_time) => monotonic_time(),
required(:duration) => duration(),
required(:wallclock_time) => DateTime.t(),
required(:status) => nil | 100..599,
optional(:req_body_bytes) => non_neg_integer()
},
%{
required(:conn) => Conn.t(),
required(:listener) => listener(),
required(:transport) => transport(),
required(:telemetry_span_context) => telemetry_span_context(),
required(:target) => target(),
required(:algorithm) => Selector.algorithm(),
required(:status) => nil | 100..599
}
)
@typedoc """
The `[:wayfarer, :request, :req_body_chunk]` event.
This event is emitted while streaming the request body from the client to the
target. Under the hood `Plug.Conn.read_body/2` is being called with the
default options, meaning that each chunk is likely to be up to 8MB in size.
If there is no request body then this event will not be emitted and the
`req_body_bytes` and `req_body_chunks` counters will both be set to zero for
this request.
"""
@type request_req_body_chunk ::
event(
%{
required(:monotonic_time) => monotonic_time(),
required(:duration) => duration(),
required(:wallclock_time) => DateTime.t(),
required(:status) => nil | 100..599,
required(:req_body_bytes) => non_neg_integer(),
required(:req_body_chunks) => non_neg_integer(),
required(:chunk_bytes) => non_neg_integer()
},
%{
required(:conn) => Conn.t(),
required(:listener) => listener(),
required(:transport) => transport(),
required(:telemetry_span_context) => telemetry_span_context(),
required(:target) => target(),
required(:algorithm) => Selector.algorithm(),
required(:status) => nil | 100..599
}
)
@typedoc """
The `[:wayfarer, :request, :resp_started]` event.
This event indicates that the HTTP status and headers have been received from
the target and the response will now start being sent to the target.
"""
@type request_resp_started ::
event(
%{
required(:monotonic_time) => monotonic_time(),
required(:duration) => duration(),
required(:wallclock_time) => DateTime.t(),
required(:status) => nil | 100..599,
optional(:req_body_bytes) => non_neg_integer(),
optional(:req_body_chunks) => non_neg_integer()
},
%{
required(:conn) => Conn.t(),
required(:listener) => listener(),
required(:transport) => transport(),
required(:telemetry_span_context) => telemetry_span_context(),
required(:target) => target(),
required(:algorithm) => Selector.algorithm(),
required(:status) => nil | 100..599
}
)
@typedoc """
The `[:wayfarer, :request, :resp_body_chunk]` event.
This event is emitted every time a chunk of response body is received from the
target for streaming to the client. Under the hood, these are emitted every
time `Mint.HTTP.stream/2` returns a data frame.
"""
@type request_resp_body_chunk ::
event(
%{
required(:monotonic_time) => monotonic_time(),
required(:duration) => duration(),
required(:wallclock_time) => DateTime.t(),
optional(:req_body_bytes) => non_neg_integer(),
optional(:req_body_chunks) => non_neg_integer(),
required(:resp_body_bytes) => non_neg_integer(),
required(:resp_body_chunks) => non_neg_integer(),
required(:chunk_bytes) => non_neg_integer()
},
%{
required(:conn) => Conn.t(),
required(:listener) => listener(),
required(:transport) => transport(),
required(:telemetry_span_context) => telemetry_span_context(),
required(:target) => target(),
required(:algorithm) => Selector.algorithm(),
required(:status) => nil | 100..599
}
)
@typedoc """
The `[:wayfarer, :request, :upgraded]` event.
This event is emitted when a client connection is upgraded to a WebSocket
connection.
"""
@type request_upgraded ::
event(
%{
required(:monotonic_time) => monotonic_time(),
required(:duration) => duration(),
required(:wallclock_time) => DateTime.t()
},
%{
required(:conn) => Conn.t(),
required(:listener) => listener(),
required(:transport) => transport(),
required(:telemetry_span_context) => telemetry_span_context(),
required(:target) => target(),
required(:algorithm) => Selector.algorithm(),
required(:status) => nil | 100..599
}
)
@typedoc """
The `[:wayfarer, :request, :client_frame]` event.
This event is emitted any time a WebSocket frame is received from the client
for transmission to the target.
"""
@type request_client_frame ::
event(
%{
required(:monotonic_time) => monotonic_time(),
required(:duration) => duration(),
required(:wallclock_time) => DateTime.t(),
required(:frame_size) => non_neg_integer(),
required(:client_frame_bytes) => non_neg_integer(),
required(:client_frame_count) => non_neg_integer(),
optional(:server_frame_bytes) => non_neg_integer(),
optional(:server_frame_count) => non_neg_integer()
},
%{
required(:conn) => Conn.t(),
required(:listener) => listener(),
required(:transport) => transport(),
required(:telemetry_span_context) => telemetry_span_context(),
required(:target) => target(),
required(:algorithm) => Selector.algorithm(),
required(:status) => nil | 100..599,
required(:opcode) => :text | :binary | :ping | :pong | :close
}
)
@typedoc """
The `[:wayfarer, :request, :server_frame]` event.
This event is emitted any time a WebSocket frame is received from the target
for transmission to the client.
"""
@type request_server_frame ::
event(
%{
required(:monotonic_time) => monotonic_time(),
required(:duration) => duration(),
required(:wallclock_time) => DateTime.t(),
required(:frame_size) => non_neg_integer(),
required(:server_frame_bytes) => non_neg_integer(),
required(:server_frame_count) => non_neg_integer(),
optional(:client_frame_bytes) => non_neg_integer(),
optional(:client_frame_count) => non_neg_integer()
},
%{
required(:conn) => Conn.t(),
required(:listener) => listener(),
required(:transport) => transport(),
required(:telemetry_span_context) => telemetry_span_context(),
required(:target) => target(),
required(:algorithm) => Selector.algorithm(),
required(:status) => nil | 100..599
}
)
@typedoc """
All the event types that make up the `[:wayfarer, :request, :*]` span.
"""
@type request_span ::
request_start
| request_routed
| request_exception
| request_stop
| request_received_status
| request_req_body_chunk
| request_resp_started
| request_resp_body_chunk
| request_upgraded
| request_client_frame
| request_server_frame
@typedoc """
The `[:wayfarer, :health_check, :start]` event.
This event signals the start of a health check span.
You can use the `telemetry_span_context` metadata value to correlate
subsequent events within the same span.
"""
@type health_check_start ::
event(
%{
required(:monotonic_time) => monotonic_time(),
required(:wallclock_time) => DateTime.t()
},
%{
required(:telemetry_span_context) => telemetry_span_context(),
required(:hostname) => String.t(),
required(:uri) => URI.t(),
required(:target) => target(),
required(:method) => String.t()
}
)
@typedoc """
The `[:wayfarer, :health_check, :connect]` event.
This event signals that the outgoing TCP connection has been made to the
target.
"""
@type health_check_connect ::
event(
%{
required(:monotonic_time) => monotonic_time(),
required(:wallclock_time) => DateTime.t(),
required(:duration) => duration()
},
%{
required(:telemetry_span_context) => telemetry_span_context(),
required(:hostname) => String.t(),
required(:uri) => URI.t(),
required(:target) => target(),
required(:method) => String.t(),
required(:transport) => transport()
}
)
@typedoc """
The `[:wayfarer, :health_check, :request]` event.
This event signals that the HTTP or WebSocket request has been sent.
"""
@type health_check_request ::
event(
%{
required(:monotonic_time) => monotonic_time(),
required(:wallclock_time) => DateTime.t(),
required(:duration) => duration()
},
%{
required(:telemetry_span_context) => telemetry_span_context(),
required(:hostname) => String.t(),
required(:uri) => URI.t(),
required(:target) => target(),
required(:method) => String.t(),
required(:transport) => transport()
}
)
@typedoc """
The `[:wayfarer, :health_check, :pass]` event.
This event signals that the HTTP status code returned by the target matches
one of the configured success codes.
It also signals the end of the span.
"""
@type health_check_pass ::
event(
%{
required(:monotonic_time) => monotonic_time(),
required(:wallclock_time) => DateTime.t(),
required(:duration) => duration(),
required(:status) => 100..599
},
%{
required(:telemetry_span_context) => telemetry_span_context(),
required(:hostname) => String.t(),
required(:uri) => URI.t(),
required(:target) => target(),
required(:method) => String.t(),
required(:transport) => transport()
}
)
@typedoc """
The `[:wayfarer, :health_check, :fail]` event.
This event signals that the HTTP status code returned by the target did not
match any of the configured success codes.
It also signals the end of the span.
"""
@type health_check_fail ::
event(
%{
required(:monotonic_time) => monotonic_time(),
required(:wallclock_time) => DateTime.t(),
required(:duration) => duration(),
required(:reason) => any
},
%{
required(:telemetry_span_context) => telemetry_span_context(),
required(:hostname) => String.t(),
required(:uri) => URI.t(),
required(:target) => target(),
required(:method) => String.t(),
required(:transport) => transport()
}
)
@typedoc """
All the event types that make up the `[:wayfarer, :health_check, :*]` span.
"""
@type health_check_span ::
health_check_start | health_check_request | health_check_pass | health_check_fail
@typedoc """
All the spans that can be emitted by Wayfarer.
"""
@type spans :: request_span | health_check_span
@doc false
@spec request_start(Conn.t()) :: Conn.t()
def request_start(conn) do
telemetry_span_context = make_ref()
metadata =
conn
|> Map.get(:private, %{})
|> Map.get(:wayfarer, %{})
|> Map.merge(%{telemetry_span_context: telemetry_span_context, conn: conn})
conn
|> execute_request_span_event(:start, %{}, metadata)
end
@doc false
@spec request_routed(Conn.t(), Router.target(), Router.algorithm()) :: Conn.t()
def request_routed(conn, target, algorithm) do
target =
case target do
{scheme, address, port, _transport} -> %{scheme: scheme, address: address, port: port}
{:plug, {module, opts}} -> %{scheme: :plug, module: module, options: opts}
{:plug, module} -> %{scheme: :plug, module: module, options: []}
end
conn
|> execute_request_span_event(:routed, %{}, %{
target: target,
algorithm: algorithm
})
end
@doc false
@spec request_exception(Conn.t(), kind :: any, reason :: any, stacktrace :: list | nil) ::
Conn.t()
def request_exception(conn, kind, reason, stacktrace \\ nil) do
conn
|> execute_request_span_event(:exception, %{}, %{
kind: kind,
reason: reason,
stacktrace: stacktrace || Process.info(self(), :current_stacktrace)
})
end
@doc false
def request_stop(conn) do
conn
|> execute_request_span_event(:stop, %{status: conn.status}, %{})
end
@doc false
@spec request_received_status(Conn.t(), non_neg_integer()) :: Conn.t()
def request_received_status(conn, status) do
conn
|> execute_request_span_event(:received_status, %{status: status}, %{status: status})
end
@doc false
@spec request_req_body_chunk(Conn.t(), non_neg_integer()) :: Conn.t()
def request_req_body_chunk(conn, chunk_bytes) do
conn
|> execute_request_span_event(:req_body_chunk, %{chunk_bytes: chunk_bytes}, %{})
end
@doc false
@spec request_resp_started(Conn.t()) :: Conn.t()
def request_resp_started(conn) do
metric = %{status: conn.status}
conn
|> execute_request_span_event(:resp_started, metric, metric)
end
@doc false
@spec request_resp_body_chunk(Conn.t(), non_neg_integer()) :: Conn.t()
def request_resp_body_chunk(conn, chunk_bytes) do
conn
|> execute_request_span_event(:resp_body_chunk, %{chunk_bytes: chunk_bytes}, %{})
end
@doc false
@spec request_upgraded(Conn.t()) :: Conn.t()
def request_upgraded(conn) do
conn
|> execute_request_span_event(:upgraded, %{}, %{})
end
@doc false
@spec request_client_frame(Conn.t(), non_neg_integer(), atom) :: Conn.t()
def request_client_frame(conn, bytes, opcode) do
conn
|> execute_request_span_event(:client_frame, %{frame_size: bytes}, %{opcode: opcode})
end
@doc false
@spec request_server_frame(Conn.t(), non_neg_integer(), atom) :: Conn.t()
def request_server_frame(conn, bytes, opcode) do
conn
|> execute_request_span_event(:server_frame, %{frame_size: bytes}, %{opcode: opcode})
end
@doc false
@spec set_metrics(Conn.t(), %{atom => number}) :: Conn.t()
def set_metrics(conn, metrics) do
update_metrics(conn, &Map.merge(&1, metrics))
end
@doc false
@spec increment_metrics(Conn.t(), %{atom => number}) :: Conn.t()
def increment_metrics(conn, to_increment) do
update_metrics(conn, fn metrics ->
Enum.reduce(to_increment, metrics, fn {metric_name, increment_by}, metrics ->
Map.update(metrics, metric_name, increment_by, &(&1 + increment_by))
end)
end)
end
@doc false
@spec health_check_start(Check.state()) :: Check.state()
def health_check_start(check) do
execute_check_span_event(check, :start, %{}, %{})
end
@doc false
@spec health_check_connect(Check.state(), atom) :: Check.state()
def health_check_connect(check, transport) do
execute_check_span_event(check, :connect, %{}, %{transport: transport})
end
@doc false
@spec health_check_request(Check.state()) :: Check.state()
def health_check_request(check) do
execute_check_span_event(check, :request, %{}, %{})
end
@doc false
@spec health_check_fail(Check.state(), any) :: Check.state()
def health_check_fail(check, reason) do
execute_check_span_event(check, :fail, %{}, %{reason: reason})
end
@doc false
@spec health_check_pass(Check.state(), 100..599) :: Check.state()
def health_check_pass(check, status) do
execute_check_span_event(check, :pass, %{}, %{status: status})
end
defp update_metrics(conn, callback) do
private =
conn
|> Map.get(:private, %{})
|> Map.get(:wayfarer, %{})
metrics =
private
|> Map.get(:metrics, %{})
|> callback.()
private = Map.put(private, :metrics, metrics)
conn
|> Conn.put_private(:wayfarer, private)
end
defp execute_request_span_event(conn, event, measurements, metadata) do
monotonic_time = System.monotonic_time()
now = DateTime.utc_now()
private =
conn
|> Map.get(:private, %{})
|> Map.get(:wayfarer, %{})
span_info =
private
|> Map.get(:request_span, %{})
metrics =
private
|> Map.get(:metrics, %{})
measurements =
if Map.has_key?(span_info, :start_time) do
%{
monotonic_time: monotonic_time,
duration: monotonic_time - span_info.start_time,
wallclock_time: now
}
else
%{
monotonic_time: monotonic_time,
wallclock_time: now
}
end
|> Map.merge(metrics)
|> Map.merge(measurements)
metadata =
span_info
|> Map.get(:metadata, %{})
|> Map.merge(metadata)
:telemetry.execute([:wayfarer, :request, event], measurements, Map.put(metadata, :conn, conn))
span_info =
span_info
|> Map.put(:metadata, metadata)
|> Map.put_new(:start_time, monotonic_time)
private =
private
|> Map.put(:request_span, span_info)
conn
|> Conn.put_private(:wayfarer, private)
end
defp execute_check_span_event(check, event, measurements, metadata) do
monotonic_time = System.monotonic_time()
now = DateTime.utc_now()
span_info = Map.get(check, :span, %{})
metrics = Map.get(span_info, :metrics, %{})
measurements =
if Map.has_key?(span_info, :start_time) do
%{
monotonic_time: monotonic_time,
duration: monotonic_time - span_info.start_time,
wallclock_time: now
}
else
%{
monotonic_time: monotonic_time,
wallclock_time: now
}
end
|> Map.merge(metrics)
|> Map.merge(measurements)
metadata =
span_info
|> Map.get(:metadata, %{})
|> Map.merge(metadata)
:telemetry.execute([:wayfarer, :health_check, event], measurements, metadata)
span_info =
span_info
|> Map.put(:metadata, metadata)
|> Map.put_new(:start_time, monotonic_time)
Map.put(check, :span, span_info)
end
end

View file

@ -5,7 +5,8 @@ defmodule Wayfarer.Utils do
@type address_input :: IP.Address.t() | String.t() | :inet.ip_address() @type address_input :: IP.Address.t() | String.t() | :inet.ip_address()
@type port_number :: 1..0xFFFF @type port_number :: 1..0xFFFF
@type scheme :: :http | :https @type scheme :: :http | :https | :ws | :wss
@type transport :: :http1 | :http2 | :auto
@doc """ @doc """
Verify an IP address and convert it into a tuple. Verify an IP address and convert it into a tuple.
@ -46,7 +47,7 @@ defmodule Wayfarer.Utils do
Verify a scheme. Verify a scheme.
""" """
@spec sanitise_scheme(scheme) :: {:ok, scheme} | {:error, any} @spec sanitise_scheme(scheme) :: {:ok, scheme} | {:error, any}
def sanitise_scheme(scheme) when scheme in [:http, :https], do: {:ok, scheme} def sanitise_scheme(scheme) when scheme in [:http, :https, :ws, :wss], do: {:ok, scheme}
def sanitise_scheme(scheme), def sanitise_scheme(scheme),
do: do:
@ -58,7 +59,7 @@ defmodule Wayfarer.Utils do
@doc """ @doc """
Convert a scheme, address, port tuple into a `URI`. Convert a scheme, address, port tuple into a `URI`.
""" """
@spec to_uri(:http | :https, address_input, port_number) :: {:ok, URI.t()} | {:error, any} @spec to_uri(scheme, address_input, port_number) :: {:ok, URI.t()} | {:error, any}
def to_uri(scheme, address, port) do def to_uri(scheme, address, port) do
with {:ok, scheme} <- sanitise_scheme(scheme), with {:ok, scheme} <- sanitise_scheme(scheme),
{:ok, address} <- sanitise_ip_address(address), {:ok, address} <- sanitise_ip_address(address),
@ -76,7 +77,7 @@ defmodule Wayfarer.Utils do
@doc """ @doc """
Convert a list of targets into a match spec guard. Convert a list of targets into a match spec guard.
""" """
@spec targets_to_ms_guard(atom, [{:http | :https, address_input, port_number}]) :: [ @spec targets_to_ms_guard(atom, [{scheme, :inet.ip_address(), port_number, transport}]) :: [
{atom, any, any} {atom, any, any}
] ]
def targets_to_ms_guard(_var, []), do: [] def targets_to_ms_guard(_var, []), do: []
@ -92,11 +93,8 @@ defmodule Wayfarer.Utils do
@doc """ @doc """
Convert a target tuple into a tuple safe for injection into a match spec. Convert a target tuple into a tuple safe for injection into a match spec.
""" """
@spec target_to_ms({:http | :https, address_input, port_number}) :: @spec target_to_ms({scheme, :inet.ip_address(), port_number, transport}) ::
{{:http | :https, address_input, port_number}} {{scheme, {:inet.ip_address()}, port_number, transport}}
| {{:http | :https, {:inet.ip_address()}, port_number}} def target_to_ms({scheme, address, port, transport}) when is_tuple(address),
def target_to_ms({scheme, address, port}) when is_tuple(address), do: {{scheme, {address}, port, transport}}
do: {{scheme, {address}, port}}
def target_to_ms({scheme, address, port}), do: {{scheme, address, port}}
end end

26
mix.exs
View file

@ -5,7 +5,7 @@ defmodule Wayfarer.MixProject do
A runtime-configurable HTTP reverse proxy based on Bandit. A runtime-configurable HTTP reverse proxy based on Bandit.
""" """
@version "0.4.0" @version "0.6.1"
def project do def project do
[ [
@ -18,17 +18,16 @@ defmodule Wayfarer.MixProject do
deps: deps(), deps: deps(),
description: @moduledoc, description: @moduledoc,
package: package(), package: package(),
source_url: "https://code.harton.nz/james/wayfarer", source_url: "https://harton.dev/james/wayfarer",
homepage_url: "https://code.harton.nz/james/wayfarer", homepage_url: "https://harton.dev/james/wayfarer",
aliases: aliases(), aliases: aliases(),
dialyzer: [plt_add_apps: []], dialyzer: [plt_ignore_apps: [:mint]],
docs: [ docs: [
main: "Wayfarer", main: "Wayfarer",
formatters: ["html"], formatters: ["html"],
extra_section: "GUIDES", extra_section: "GUIDES",
filter_modules: ~r/^Elixir.Wayfarer/, filter_modules: ~r/^Elixir.Wayfarer/,
source_url_pattern: source_url_pattern: "https://harton.dev/james/wayfarer/src/branch/main/%{path}#L%{line}",
"https://code.harton.nz/james/wayfarer/src/branch/main/%{path}#L%{line}",
spark: [ spark: [
extensions: [ extensions: [
%{ %{
@ -71,9 +70,12 @@ defmodule Wayfarer.MixProject do
maintainers: ["James Harton <james@harton.nz>"], maintainers: ["James Harton <james@harton.nz>"],
licenses: ["HL3-FULL"], licenses: ["HL3-FULL"],
links: %{ links: %{
"Source" => "https://code.harton.nz/james/wayfarer" "Source" => "https://harton.dev/james/wayfarer",
"GitHub" => "https://github.com/jimsynz/wayfarer",
"Changelog" => "https://docs.harton.nz/james/wayfarer/changelog.html",
"Sponsor" => "https://github.com/sponsors/jimsynz"
}, },
source_url: "https://code.harton.nz/james/wayfarer" source_url: "https://harton.dev/james/wayfarer"
] ]
end end
@ -94,20 +96,22 @@ defmodule Wayfarer.MixProject do
{:castore, "~> 1.0"}, {:castore, "~> 1.0"},
{:ip, "~> 2.0"}, {:ip, "~> 2.0"},
{:mint, "~> 1.5"}, {:mint, "~> 1.5"},
{:mint_web_socket, "~> 1.0"},
{:nimble_options, "~> 1.0"}, {:nimble_options, "~> 1.0"},
{:plug, "~> 1.15"}, {:plug, "~> 1.15"},
{:spark, "~> 1.1"}, {:spark, "~> 2.0"},
{:telemetry, "~> 1.2"}, {:telemetry, "~> 1.2"},
{:websock, "~> 0.5"}, {:websock, "~> 0.5"},
{:websock_adapter, "~> 0.5"},
# Dev/test # Dev/test
{:credo, "~> 1.7", opts}, {:credo, "~> 1.7", opts},
{:dialyxir, "~> 1.3", opts}, {:dialyxir, "~> 1.3", opts},
{:doctor, "~> 0.21", opts}, {:doctor, "~> 0.21", opts},
{:earmark, ">= 0.0.0", opts}, {:earmark, ">= 0.0.0", opts},
{:ex_check, "~> 0.15", opts}, {:ex_check, "~> 0.16", opts},
{:ex_doc, ">= 0.0.0", opts}, {:ex_doc, ">= 0.0.0", opts},
{:faker, "~> 0.17", opts}, {:faker, "~> 0.18", opts},
{:git_ops, "~> 2.6", opts}, {:git_ops, "~> 2.6", opts},
{:mimic, "~> 1.7", Keyword.delete(opts, :runtime)}, {:mimic, "~> 1.7", Keyword.delete(opts, :runtime)},
{:mix_audit, "~> 2.1", opts} {:mix_audit, "~> 2.1", opts}

View file

@ -1,39 +1,46 @@
%{ %{
"bandit": {:hex, :bandit, "1.1.0", "1414e65916229d4ee0914f6d4e7f8ec16c6f2d90e01ad5174d89e90baa577625", [:mix], [{:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "4891fb2f48a83445da70a4e949f649a9b4032310f1f640f4a8a372bc91cece18"}, "bandit": {:hex, :bandit, "1.5.7", "6856b1e1df4f2b0cb3df1377eab7891bec2da6a7fd69dc78594ad3e152363a50", [:mix], [{:hpax, "~> 1.0.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "f2dd92ae87d2cbea2fa9aa1652db157b6cba6c405cb44d4f6dd87abba41371cd"},
"bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
"castore": {:hex, :castore, "1.0.4", "ff4d0fb2e6411c0479b1d965a814ea6d00e51eb2f58697446e9c41a97d940b28", [:mix], [], "hexpm", "9418c1b8144e11656f0be99943db4caf04612e3eaecefb5dae9a2a87565584f8"}, "castore": {:hex, :castore, "1.0.8", "dedcf20ea746694647f883590b82d9e96014057aff1d44d03ec90f36a5c0dc6e", [:mix], [], "hexpm", "0b2b66d2ee742cb1d9cb8c8be3b43c3a70ee8651f37b75a8b982e036752983f1"},
"credo": {:hex, :credo, "1.7.1", "6e26bbcc9e22eefbff7e43188e69924e78818e2fe6282487d0703652bc20fd62", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "e9871c6095a4c0381c89b6aa98bc6260a8ba6addccf7f6a53da8849c748a58a2"}, "credo": {:hex, :credo, "1.7.7", "771445037228f763f9b2afd612b6aa2fd8e28432a95dbbc60d8e03ce71ba4446", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8bc87496c9aaacdc3f90f01b7b0582467b69b4bd2441fe8aae3109d843cc2f2e"},
"decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"},
"dialyxir": {:hex, :dialyxir, "1.4.1", "a22ed1e7bd3a3e3f197b68d806ef66acb61ee8f57b3ac85fc5d57354c5482a93", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "84b795d6d7796297cca5a3118444b80c7d94f7ce247d49886e7c291e1ae49801"}, "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"},
"doctor": {:hex, :doctor, "0.21.0", "20ef89355c67778e206225fe74913e96141c4d001cb04efdeba1a2a9704f1ab5", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "a227831daa79784eb24cdeedfa403c46a4cb7d0eab0e31232ec654314447e4e0"}, "doctor": {:hex, :doctor, "0.21.0", "20ef89355c67778e206225fe74913e96141c4d001cb04efdeba1a2a9704f1ab5", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "a227831daa79784eb24cdeedfa403c46a4cb7d0eab0e31232ec654314447e4e0"},
"earmark": {:hex, :earmark, "1.4.46", "8c7287bd3137e99d26ae4643e5b7ef2129a260e3dcf41f251750cb4563c8fb81", [:mix], [], "hexpm", "798d86db3d79964e759ddc0c077d5eb254968ed426399fbf5a62de2b5ff8910a"}, "earmark": {:hex, :earmark, "1.4.47", "7e7596b84fe4ebeb8751e14cbaeaf4d7a0237708f2ce43630cfd9065551f94ca", [:mix], [], "hexpm", "3e96bebea2c2d95f3b346a7ff22285bc68a99fbabdad9b655aa9c6be06c698f8"},
"earmark_parser": {:hex, :earmark_parser, "1.4.37", "2ad73550e27c8946648b06905a57e4d454e4d7229c2dafa72a0348c99d8be5f7", [:mix], [], "hexpm", "6b19783f2802f039806f375610faa22da130b8edc21209d0bff47918bb48360e"}, "earmark_parser": {:hex, :earmark_parser, "1.4.40", "f3534689f6b58f48aa3a9ac850d4f05832654fe257bf0549c08cc290035f70d5", [:mix], [], "hexpm", "cdb34f35892a45325bad21735fadb88033bcb7c4c296a999bde769783f53e46a"},
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
"ex_check": {:hex, :ex_check, "0.15.0", "074b94c02de11c37bba1ca82ae5cc4926e6ccee862e57a485b6ba60fca2d8dc1", [:mix], [], "hexpm", "33848031a0c7e4209c3b4369ce154019788b5219956220c35ca5474299fb6a0e"}, "ex_check": {:hex, :ex_check, "0.16.0", "07615bef493c5b8d12d5119de3914274277299c6483989e52b0f6b8358a26b5f", [:mix], [], "hexpm", "4d809b72a18d405514dda4809257d8e665ae7cf37a7aee3be6b74a34dec310f5"},
"ex_doc": {:hex, :ex_doc, "0.30.6", "5f8b54854b240a2b55c9734c4b1d0dd7bdd41f71a095d42a70445c03cf05a281", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "bd48f2ddacf4e482c727f9293d9498e0881597eae6ddc3d9562bd7923375109f"}, "ex_doc": {:hex, :ex_doc, "0.34.2", "13eedf3844ccdce25cfd837b99bea9ad92c4e511233199440488d217c92571e8", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "5ce5f16b41208a50106afed3de6a2ed34f4acfd65715b82a0b84b49d995f95c1"},
"faker": {:hex, :faker, "0.17.0", "671019d0652f63aefd8723b72167ecdb284baf7d47ad3a82a15e9b8a6df5d1fa", [:mix], [], "hexpm", "a7d4ad84a93fd25c5f5303510753789fc2433ff241bf3b4144d3f6f291658a6a"}, "faker": {:hex, :faker, "0.18.0", "943e479319a22ea4e8e39e8e076b81c02827d9302f3d32726c5bf82f430e6e14", [:mix], [], "hexpm", "bfbdd83958d78e2788e99ec9317c4816e651ad05e24cfd1196ce5db5b3e81797"},
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, "file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"},
"git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"},
"git_ops": {:hex, :git_ops, "2.6.0", "e0791ee1cf5db03f2c61b7ebd70e2e95cba2bb9b9793011f26609f22c0900087", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "b98fca849b18aaf490f4ac7d1dd8c6c469b0cc3e6632562d366cab095e666ffe"}, "git_ops": {:hex, :git_ops, "2.6.1", "cc7799a68c26cf814d6d1a5121415b4f5bf813de200908f930b27a2f1fe9dad5", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "ce62d07e41fe993ec22c35d5edb11cf333a21ddaead6f5d9868fcb607d42039e"},
"hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"}, "glob_ex": {:hex, :glob_ex, "0.1.8", "f7ef872877ca2ae7a792ab1f9ff73d9c16bf46ecb028603a8a3c5283016adc07", [:mix], [], "hexpm", "9e39d01729419a60a937c9260a43981440c43aa4cadd1fa6672fecd58241c464"},
"ip": {:hex, :ip, "2.0.1", "c0b77da86e62cb28ae85132e84881bdb6e067be4a74de74a95db6637482b03e9", [:mix], [], "hexpm", "431d9cca05c4f835d592c3b1e59b44910e28437698e4f965119f50b78347b4fd"}, "ham": {:hex, :ham, "0.3.0", "7cd031b4a55fba219c11553e7b13ba73bd86eab4034518445eff1e038cb9a44d", [:mix], [], "hexpm", "7d6c6b73d7a6a83233876cc1b06a4d9b5de05562b228effda4532f9a49852bf6"},
"jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, "hpax": {:hex, :hpax, "1.0.0", "28dcf54509fe2152a3d040e4e3df5b265dcb6cb532029ecbacf4ce52caea3fd2", [:mix], [], "hexpm", "7f1314731d711e2ca5fdc7fd361296593fc2542570b3105595bb0bc6d0fad601"},
"makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "igniter": {:hex, :igniter, "0.3.37", "ad4ec1c0d73dedf5514ac52c5e93d5daa64bf4037a17088a9a7f4d44133a5846", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "727b74a67df63cbe4c21a99707e02c50f4b7740c93cd3431fa9184a863eb064c"},
"makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, "ip": {:hex, :ip, "2.0.3", "290d71c05b79ad62c99d8fe175e86130dc120489d119b8c2819cec16bad3c77c", [:mix], [], "hexpm", "19fa2f9c6f5cb288ca2192499888bd96f88af3564eaa7bbcfc1231ffdc5df8c2"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
"mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"}, "makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"},
"mimic": {:hex, :mimic, "1.7.4", "cd2772ffbc9edefe964bc668bfd4059487fa639a5b7f1cbdf4fd22946505aa4f", [:mix], [], "hexpm", "437c61041ecf8a7fae35763ce89859e4973bb0666e6ce76d75efc789204447c3"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"},
"mint": {:hex, :mint, "1.5.1", "8db5239e56738552d85af398798c80648db0e90f343c8469f6c6d8898944fb6f", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "4a63e1e76a7c3956abd2c72f370a0d0aecddc3976dea5c27eccbecfa5e7d5b1e"}, "makeup_erlang": {:hex, :makeup_erlang, "1.0.0", "6f0eff9c9c489f26b69b61440bf1b238d95badae49adac77973cbacae87e3c2e", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "ea7a9307de9d1548d2a72d299058d1fd2339e3d398560a0e46c27dab4891e4d2"},
"mix_audit": {:hex, :mix_audit, "2.1.1", "653aa6d8f291fc4b017aa82bdb79a4017903902ebba57960ef199cbbc8c008a1", [:make, :mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.9", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "541990c3ab3a7bb8c4aaa2ce2732a4ae160ad6237e5dcd5ad1564f4f85354db1"}, "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"},
"nimble_options": {:hex, :nimble_options, "1.0.2", "92098a74df0072ff37d0c12ace58574d26880e522c22801437151a159392270e", [:mix], [], "hexpm", "fd12a8db2021036ce12a309f26f564ec367373265b53e25403f0ee697380f1b8"}, "mimic": {:hex, :mimic, "1.10.1", "c1e3b2044483ffa54d9e61e3be439528f47022548f6d8db1f22ca7db5490e4fa", [:mix], [{:ham, "~> 0.2", [hex: :ham, repo: "hexpm", optional: false]}], "hexpm", "b31ac70e0d6f5877af03004f02632b4fbc6abe71ed95a47d87b68d3dfffb83b5"},
"nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"}, "mint": {:hex, :mint, "1.6.2", "af6d97a4051eee4f05b5500671d47c3a67dac7386045d87a904126fd4bbcea2e", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "5ee441dffc1892f1ae59127f74afe8fd82fda6587794278d924e4d90ea3d63f9"},
"plug": {:hex, :plug, "1.15.1", "b7efd81c1a1286f13efb3f769de343236bd8b7d23b4a9f40d3002fc39ad8f74c", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "459497bd94d041d98d948054ec6c0b76feacd28eec38b219ca04c0de13c79d30"}, "mint_web_socket": {:hex, :mint_web_socket, "1.0.4", "0b539116dbb3d3f861cdf5e15e269a933cb501c113a14db7001a3157d96ffafd", [:mix], [{:mint, ">= 1.4.1 and < 2.0.0-0", [hex: :mint, repo: "hexpm", optional: false]}], "hexpm", "027d4c5529c45a4ba0ce27a01c0f35f284a5468519c045ca15f43decb360a991"},
"plug_crypto": {:hex, :plug_crypto, "2.0.0", "77515cc10af06645abbfb5e6ad7a3e9714f805ae118fa1a70205f80d2d70fe73", [:mix], [], "hexpm", "53695bae57cc4e54566d993eb01074e4d894b65a3766f1c43e2c61a1b0f45ea9"}, "mix_audit": {:hex, :mix_audit, "2.1.4", "0a23d5b07350cdd69001c13882a4f5fb9f90fbd4cbf2ebc190a2ee0d187ea3e9", [:make, :mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "fd807653cc8c1cada2911129c7eb9e985e3cc76ebf26f4dd628bb25bbcaa7099"},
"sourceror": {:hex, :sourceror, "0.14.0", "b6b8552d0240400d66b6f107c1bab7ac1726e998efc797f178b7b517e928e314", [:mix], [], "hexpm", "809c71270ad48092d40bbe251a133e49ae229433ce103f762a2373b7a10a8d8b"}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
"spark": {:hex, :spark, "1.1.48", "64b804711818526e371d12ea3acc886365b14239565e361001aad801a38bad85", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "3215a8b1bb1dc93945ce9a0f68430d7265ea596c6b911f7bd6dba77b65cee370"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"},
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, "plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"},
"thousand_island": {:hex, :thousand_island, "1.1.0", "dcc115650adc61c5e7de12619f0cb94b2b8f050326e7f21ffbf6fdeb3d291e4c", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7745cf71520d74e119827ff32c2da6307e822cf835bebed3b2c459cc57f32d21"}, "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"},
"rewrite": {:hex, :rewrite, "0.10.5", "6afadeae0b9d843b27ac6225e88e165884875e0aed333ef4ad3bf36f9c101bed", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "51cc347a4269ad3a1e7a2c4122dbac9198302b082f5615964358b4635ebf3d4f"},
"sourceror": {:hex, :sourceror, "1.6.0", "9907884e1449a4bd7dbaabe95088ed4d9a09c3c791fb0103964e6316bc9448a7", [:mix], [], "hexpm", "e90aef8c82dacf32c89c8ef83d1416fc343cd3e5556773eeffd2c1e3f991f699"},
"spark": {:hex, :spark, "2.2.29", "a52733ff72b05a674e48d3ca7a4172fe7bec81e9116069da8b4db19030d581d9", [:mix], [{:igniter, ">= 0.3.36 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "111a0dadbb27537c7629bc03ac56fcab15056ab0b9ad985084b9adcdb48836c8"},
"spitfire": {:hex, :spitfire, "0.1.3", "7ea0f544005dfbe48e615ed90250c9a271bfe126914012023fd5e4b6b82b7ec7", [:mix], [], "hexpm", "d53b5107bcff526a05c5bb54c95e77b36834550affd5830c9f58760e8c543657"},
"telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
"thousand_island": {:hex, :thousand_island, "1.3.5", "6022b6338f1635b3d32406ff98d68b843ba73b3aa95cfc27154223244f3a6ca5", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2be6954916fdfe4756af3239fb6b6d75d0b8063b5df03ba76fd8a4c87849e180"},
"websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},
"websock_adapter": {:hex, :websock_adapter, "0.5.7", "65fa74042530064ef0570b75b43f5c49bb8b235d6515671b3d250022cb8a1f9e", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "d0f478ee64deddfec64b800673fd6e0c8888b079d9f3444dd96d2a98383bdbd1"},
"yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"}, "yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"},
"yaml_elixir": {:hex, :yaml_elixir, "2.9.0", "9a256da867b37b8d2c1ffd5d9de373a4fda77a32a45b452f1708508ba7bbcb53", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "0cb0e7d4c56f5e99a6253ed1a670ed0e39c13fc45a6da054033928607ac08dfc"}, "yaml_elixir": {:hex, :yaml_elixir, "2.11.0", "9e9ccd134e861c66b84825a3542a1c22ba33f338d82c07282f4f1f52d847bd50", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "53cc28357ee7eb952344995787f4bb8cc3cecbf189652236e9b163e8ce1bc242"},
} }

View file

@ -4,25 +4,22 @@ defmodule Support.Example do
config "Example" do config "Example" do
listeners do listeners do
# http "127.0.0.1", 8080 http "0.0.0.0", 8000
http "0.0.0.0", 8080
end end
targets do targets do
http "127.0.0.1", 8082 http "127.0.0.1", 4000
http "192.168.4.26", 80
end end
health_checks do health_checks do
check do check do
interval :timer.seconds(5) interval :timer.seconds(5)
success_codes 200..399
end end
end end
host_patterns do host_patterns do
pattern "*.example.com" pattern "localhost"
pattern "example.com"
end end
end end
end end

View file

@ -48,7 +48,7 @@ defmodule Wayfarer.RouterTest do
{:ok, table} = Router.init(Support.Example) {:ok, table} = Router.init(Support.Example)
listener = {:http, {127, 0, 0, 1}, random_port()} listener = {:http, {127, 0, 0, 1}, random_port()}
target = {:http, {127, 0, 0, 1}, random_port()} target = {:http, {127, 0, 0, 1}, random_port(), :auto}
assert :ok = Router.add_route(table, listener, target, ["*.example.com"], :round_robin) assert :ok = Router.add_route(table, listener, target, ["*.example.com"], :round_robin)
@ -61,7 +61,7 @@ defmodule Wayfarer.RouterTest do
{:ok, table} = Router.init(Support.Example) {:ok, table} = Router.init(Support.Example)
listener = {:http, {127, 0, 0, 1}, random_port()} listener = {:http, {127, 0, 0, 1}, random_port()}
target = {:http, {127, 0, 0, 1}, random_port()} target = {:http, {127, 0, 0, 1}, random_port(), :auto}
assert :ok = Router.add_route(table, listener, target, ["www.example.com"], :round_robin) assert :ok = Router.add_route(table, listener, target, ["www.example.com"], :round_robin)
@ -74,7 +74,7 @@ defmodule Wayfarer.RouterTest do
{:ok, table} = Router.init(Support.Example) {:ok, table} = Router.init(Support.Example)
listener = {:http, {127, 0, 0, 1}, random_port()} listener = {:http, {127, 0, 0, 1}, random_port()}
target = {:http, {127, 0, 0, 1}, random_port()} target = {:http, {127, 0, 0, 1}, random_port(), :auto}
assert :ok = assert :ok =
Router.add_route( Router.add_route(
@ -114,9 +114,9 @@ defmodule Wayfarer.RouterTest do
{:ok, table} = Router.init(Support.Example) {:ok, table} = Router.init(Support.Example)
listener0 = {:http, {127, 0, 0, 1}, random_port()} listener0 = {:http, {127, 0, 0, 1}, random_port()}
target0 = {:http, {127, 0, 0, 1}, random_port()} target0 = {:http, {127, 0, 0, 1}, random_port(), :auto}
listener1 = {:http, {127, 0, 0, 1}, random_port()} listener1 = {:http, {127, 0, 0, 1}, random_port()}
target1 = {:http, {127, 0, 0, 1}, random_port()} target1 = {:http, {127, 0, 0, 1}, random_port(), :auto}
assert :ok = assert :ok =
Router.import_routes(table, [ Router.import_routes(table, [
@ -142,9 +142,9 @@ defmodule Wayfarer.RouterTest do
{:ok, table} = Router.init(Support.Example) {:ok, table} = Router.init(Support.Example)
listener0 = {:http, {127, 0, 0, 1}, random_port()} listener0 = {:http, {127, 0, 0, 1}, random_port()}
target0 = {:http, {127, 0, 0, 1}, random_port()} target0 = {:http, {127, 0, 0, 1}, random_port(), :auto}
listener1 = {:http, {127, 0, 0, 1}, random_port()} listener1 = {:http, {127, 0, 0, 1}, random_port()}
target1 = {:http, {127, 0, 0, 1}, random_port()} target1 = {:http, {127, 0, 0, 1}, random_port(), :auto}
Router.import_routes(table, [ Router.import_routes(table, [
{listener0, target0, ["0.example.com"], :round_robin}, {listener0, target0, ["0.example.com"], :round_robin},
@ -164,9 +164,9 @@ defmodule Wayfarer.RouterTest do
{:ok, table} = Router.init(Support.Example) {:ok, table} = Router.init(Support.Example)
listener0 = {:http, {127, 0, 0, 1}, random_port()} listener0 = {:http, {127, 0, 0, 1}, random_port()}
target0 = {:http, {127, 0, 0, 1}, random_port()} target0 = {:http, {127, 0, 0, 1}, random_port(), :auto}
listener1 = {:http, {127, 0, 0, 1}, random_port()} listener1 = {:http, {127, 0, 0, 1}, random_port()}
target1 = {:http, {127, 0, 0, 1}, random_port()} target1 = {:http, {127, 0, 0, 1}, random_port(), :auto}
Router.import_routes(table, [ Router.import_routes(table, [
{listener0, target0, ["0.example.com"], :round_robin}, {listener0, target0, ["0.example.com"], :round_robin},
@ -186,9 +186,9 @@ defmodule Wayfarer.RouterTest do
{:ok, table} = Router.init(Support.Example) {:ok, table} = Router.init(Support.Example)
listener0 = {:http, {127, 0, 0, 1}, random_port()} listener0 = {:http, {127, 0, 0, 1}, random_port()}
target0 = {:http, {127, 0, 0, 1}, random_port()} target0 = {:http, {127, 0, 0, 1}, random_port(), :auto}
listener1 = {:http, {127, 0, 0, 1}, random_port()} listener1 = {:http, {127, 0, 0, 1}, random_port()}
target1 = {:http, {127, 0, 0, 1}, random_port()} target1 = {:http, {127, 0, 0, 1}, random_port(), :auto}
Router.import_routes(table, [ Router.import_routes(table, [
{listener0, target0, ["0.example.com"], :round_robin}, {listener0, target0, ["0.example.com"], :round_robin},
@ -215,8 +215,8 @@ defmodule Wayfarer.RouterTest do
{:ok, table} = Router.init(Support.Example) {:ok, table} = Router.init(Support.Example)
listener = {:http, {127, 0, 0, 1}, random_port()} listener = {:http, {127, 0, 0, 1}, random_port()}
target0 = {:http, {127, 0, 0, 1}, random_port()} target0 = {:http, {127, 0, 0, 1}, random_port(), :auto}
target1 = {:http, {127, 0, 0, 1}, random_port()} target1 = {:http, {127, 0, 0, 1}, random_port(), :auto}
:ok = :ok =
Router.import_routes(table, [ Router.import_routes(table, [

View file

@ -1,6 +1,6 @@
defmodule Wayfarer.Server.PlugTest do defmodule Wayfarer.Server.PlugTest do
@moduledoc false @moduledoc false
use ExUnit.Case, async: true use ExUnit.Case, async: false
use Mimic use Mimic
use Plug.Test use Plug.Test
use Support.PortTracker use Support.PortTracker
@ -52,7 +52,7 @@ defmodule Wayfarer.Server.PlugTest do
test "it looks for healthy targets in the router" do test "it looks for healthy targets in the router" do
listener = {:http, {127, 0, 0, 1}, random_port()} listener = {:http, {127, 0, 0, 1}, random_port()}
target = {:http, {127, 0, 0, 1}, random_port()} target = {:http, {127, 0, 0, 1}, random_port(), :auto}
{:ok, _} = HttpServer.start_link(elem(target, 2), 200, "OK") {:ok, _} = HttpServer.start_link(elem(target, 2), 200, "OK")
@ -76,7 +76,7 @@ defmodule Wayfarer.Server.PlugTest do
test "it selects a target to proxy to" do test "it selects a target to proxy to" do
listener = {:http, {127, 0, 0, 1}, random_port()} listener = {:http, {127, 0, 0, 1}, random_port()}
target = {:http, {127, 0, 0, 1}, random_port()} target = {:http, {127, 0, 0, 1}, random_port(), :auto}
{:ok, _} = HttpServer.start_link(elem(target, 2), 200, "OK") {:ok, _} = HttpServer.start_link(elem(target, 2), 200, "OK")

View file

@ -49,17 +49,18 @@ defmodule Wayfarer.Server.ProxyTest do
HTTP HTTP
|> stub(:connect, fn _, _, _, _ -> {:ok, :fake_conn} end) |> stub(:connect, fn _, _, _, _ -> {:ok, :fake_conn} end)
|> stub(:stream_request_body, fn mint, _, _ -> {:ok, mint} end)
|> stub(:request, fn mint, _, _, _, _ -> |> stub(:request, fn mint, _, _, _, _ ->
send(self(), :ignore) send(self(), :ignore)
{:ok, mint, req} {:ok, mint, req}
end) end)
|> stub(:stream, fn mint, :ignore -> {:ok, mint, responses} end) |> stub(:stream, fn mint, :ignore -> {:ok, mint, responses} end)
|> stub(:stream, fn mint, _ -> {:ok, mint, [{:done, req}]} end) |> stub(:stream, fn mint, _ -> {:ok, mint, [{:status, req, 200}, {:done, req}]} end)
end end
describe "request/2" do describe "request/2" do
test "it opens an HTTP connection to the target and sends the request", %{conn: conn} do test "it opens an HTTP connection to the target and sends the request", %{conn: conn} do
target = {:http, {127, 0, 0, 1}, random_port()} target = {:http, {127, 0, 0, 1}, random_port(), :auto}
req = make_ref() req = make_ref()
HTTP HTTP
@ -72,12 +73,13 @@ defmodule Wayfarer.Server.ProxyTest do
{:ok, :fake_conn} {:ok, :fake_conn}
end) end)
|> expect(:stream_request_body, fn mint, _, _ -> {:ok, mint} end)
|> expect(:request, fn mint, _, _, _, _ -> |> expect(:request, fn mint, _, _, _, _ ->
send(self(), :ignore) send(self(), :ignore)
{:ok, mint, req} {:ok, mint, req}
end) end)
|> expect(:stream, fn mint, :ignore -> |> expect(:stream, fn mint, :ignore ->
{:ok, mint, [{:done, req}]} {:ok, mint, [{:status, req, 200}, {:done, req}]}
end) end)
assert conn = Proxy.request(conn, target) assert conn = Proxy.request(conn, target)
@ -85,7 +87,7 @@ defmodule Wayfarer.Server.ProxyTest do
end end
test "it records the outgoing connection", %{conn: conn} do test "it records the outgoing connection", %{conn: conn} do
target = {:http, {127, 0, 0, 1}, random_port()} target = {:http, {127, 0, 0, 1}, random_port(), :auto}
ActiveConnections ActiveConnections
|> expect(:connect, fn ^target -> :ok end) |> expect(:connect, fn ^target -> :ok end)
@ -103,7 +105,7 @@ defmodule Wayfarer.Server.ProxyTest do
{:error, %Mint.TransportError{reason: :protocol_not_negotiated}} {:error, %Mint.TransportError{reason: :protocol_not_negotiated}}
end) end)
target = {:http, {127, 0, 0, 1}, random_port()} target = {:http, {127, 0, 0, 1}, random_port(), :auto}
assert conn = Proxy.request(conn, target) assert conn = Proxy.request(conn, target)
assert conn.status == 502 assert conn.status == 502
end end
@ -116,7 +118,7 @@ defmodule Wayfarer.Server.ProxyTest do
{:error, %Mint.TransportError{reason: :timeout}} {:error, %Mint.TransportError{reason: :timeout}}
end) end)
target = {:http, {127, 0, 0, 1}, random_port()} target = {:http, {127, 0, 0, 1}, random_port(), :auto}
assert conn = Proxy.request(conn, target) assert conn = Proxy.request(conn, target)
assert conn.status == 504 assert conn.status == 504
end end
@ -141,7 +143,7 @@ defmodule Wayfarer.Server.ProxyTest do
{:error, :ignore} {:error, :ignore}
end) end)
target = {:http, {127, 0, 0, 1}, random_port()} target = {:http, {127, 0, 0, 1}, random_port(), :auto}
Proxy.request(conn, target) Proxy.request(conn, target)
end end
@ -176,7 +178,7 @@ defmodule Wayfarer.Server.ProxyTest do
{:error, :ignore} {:error, :ignore}
end) end)
target = {:http, {127, 0, 0, 1}, random_port()} target = {:http, {127, 0, 0, 1}, random_port(), :auto}
Proxy.request(conn, target) Proxy.request(conn, target)
end end
@ -205,7 +207,7 @@ defmodule Wayfarer.Server.ProxyTest do
{:error, :ignore} {:error, :ignore}
end) end)
target = {:http, {127, 0, 0, 1}, random_port()} target = {:http, {127, 0, 0, 1}, random_port(), :auto}
Proxy.request(conn, target) Proxy.request(conn, target)
end end
end end

View file

@ -54,7 +54,7 @@ defmodule Wayfarer.ServerTest do
routing_table: [ routing_table: [
{ {
{:http, ~i"127.0.0.1", listen_port}, {:http, ~i"127.0.0.1", listen_port},
{:http, ~i"127.0.0.1", target_port}, {:http, ~i"127.0.0.1", target_port, :auto},
["example.com"], ["example.com"],
:round_robin :round_robin
} }
@ -63,7 +63,7 @@ defmodule Wayfarer.ServerTest do
assert :ets.tab2list(Support.Example) == [ assert :ets.tab2list(Support.Example) == [
{{:http, {127, 0, 0, 1}, listen_port}, {"example", "com"}, {{:http, {127, 0, 0, 1}, listen_port}, {"example", "com"},
{:http, {127, 0, 0, 1}, target_port}, :round_robin, :initial} {:http, {127, 0, 0, 1}, target_port, :auto}, :round_robin, :initial}
] ]
end end
end end

View file

@ -1,6 +1,6 @@
defmodule Wayfarer.Target.SelectorTest do defmodule Wayfarer.Target.SelectorTest do
@moduledoc false @moduledoc false
use ExUnit.Case, async: true use ExUnit.Case, async: false
use Plug.Test use Plug.Test
use Mimic use Mimic
use Support.PortTracker use Support.PortTracker

View file

@ -51,9 +51,9 @@ defmodule WayfarerTest do
] ]
], ],
routing_table: [ routing_table: [
{{:http, ~i"127.0.0.1", listener_port}, {:http, ~i"127.0.0.1", target0_port}, {{:http, ~i"127.0.0.1", listener_port}, {:http, ~i"127.0.0.1", target0_port, :auto},
["www.example.com"], :round_robin}, ["www.example.com"], :round_robin},
{{:http, ~i"127.0.0.1", listener_port}, {:http, ~i"127.0.0.1", target1_port}, {{:http, ~i"127.0.0.1", listener_port}, {:http, ~i"127.0.0.1", target1_port, :auto},
["www.example.com"], :round_robin} ["www.example.com"], :round_robin}
] ]
) )
@ -69,8 +69,8 @@ defmodule WayfarerTest do
assert [1, 1] = assert [1, 1] =
[ [
{:http, {127, 0, 0, 1}, target0_port}, {:http, {127, 0, 0, 1}, target0_port, :auto},
{:http, {127, 0, 0, 1}, target1_port} {:http, {127, 0, 0, 1}, target1_port, :auto}
] ]
|> TotalConnections.proxy_count() |> TotalConnections.proxy_count()
|> Enum.map(&elem(&1, 1)) |> Enum.map(&elem(&1, 1))
@ -85,8 +85,8 @@ defmodule WayfarerTest do
assert [11, 11] = assert [11, 11] =
[ [
{:http, {127, 0, 0, 1}, target0_port}, {:http, {127, 0, 0, 1}, target0_port, :auto},
{:http, {127, 0, 0, 1}, target1_port} {:http, {127, 0, 0, 1}, target1_port, :auto}
] ]
|> TotalConnections.proxy_count() |> TotalConnections.proxy_count()
|> Enum.map(&elem(&1, 1)) |> Enum.map(&elem(&1, 1))