From d40b443e1869fe972159022803c1f23a6b8d6832 Mon Sep 17 00:00:00 2001 From: chen Date: Mon, 4 Sep 2023 16:17:26 +0800 Subject: [PATCH 01/22] add structure for new doc --- docs/01.Getting-Started/1.1.Quick-Start.md | 5 +++++ docs/01.Getting-Started/1.2.Install.md | 3 +++ docs/01.Getting-Started/1.3.Concepts.md | 4 ++++ docs/01.Getting-Started/README.md | 5 +++++ docs/02.Tutorials/2.1.egctl-Usage.md | 3 +++ docs/02.Tutorials/2.2.HTTP-Proxy-Usage.md | 5 +++++ docs/02.Tutorials/2.3.Pipeline-Explained.md | 6 ++++++ docs/02.Tutorials/2.4.Resilience.md | 3 +++ docs/02.Tutorials/2.5.HTTPS-Lets-Encrypt.md | 3 +++ docs/02.Tutorials/2.6.Websocket.md | 3 +++ docs/02.Tutorials/2.7gRPC.md | 3 +++ docs/02.Tutorials/README.md | 9 +++++++++ docs/03.Advanced-Cookbook/3.1.MQTT-Proxy.md | 3 +++ docs/03.Advanced-Cookbook/3.10.Performance.md | 3 +++ docs/03.Advanced-Cookbook/3.11.Migrate.md | 3 +++ docs/03.Advanced-Cookbook/3.2.Flash-Sale.md | 3 +++ .../3.3.Telegram-Translation-Bot.md | 3 +++ docs/03.Advanced-Cookbook/3.4.Canary-Release.md | 3 +++ .../03.Advanced-Cookbook/3.5.Distributed-Tracing.md | 3 +++ docs/03.Advanced-Cookbook/3.6.Service-Proxy.md | 3 +++ docs/03.Advanced-Cookbook/3.7.WasmHost.md | 3 +++ docs/03.Advanced-Cookbook/3.8.FaaS.md | 3 +++ docs/03.Advanced-Cookbook/3.9.Workflow.md | 3 +++ docs/03.Advanced-Cookbook/README.md | 13 +++++++++++++ .../4.1.Kubernetes-Ingress-Controller.md | 3 +++ docs/04.Cloud-Native/4.2.Gateway-API.md | 3 +++ docs/04.Cloud-Native/4.3.EaseMesh.md | 3 +++ docs/04.Cloud-Native/README.md | 5 +++++ docs/05.Administration/5.1.Cluster-Deployment.md | 3 +++ docs/05.Administration/README.md | 3 +++ .../6.1.Developer-Guide.md | 0 docs/06.Development-for-Easegress/6.2.Code.md | 0 .../06.Development-for-Easegress/6.3.Custom-Data.md | 3 +++ docs/06.Development-for-Easegress/6.4.egbuilder.md | 3 +++ docs/06.Development-for-Easegress/README.md | 6 ++++++ docs/07.Reference/README.md | 7 +++++++ docs/08.FAQ/README.md | 0 docs/README.md | 10 ++++++++++ 38 files changed, 147 insertions(+) create mode 100644 docs/01.Getting-Started/1.1.Quick-Start.md create mode 100644 docs/01.Getting-Started/1.2.Install.md create mode 100644 docs/01.Getting-Started/1.3.Concepts.md create mode 100644 docs/01.Getting-Started/README.md create mode 100644 docs/02.Tutorials/2.1.egctl-Usage.md create mode 100644 docs/02.Tutorials/2.2.HTTP-Proxy-Usage.md create mode 100644 docs/02.Tutorials/2.3.Pipeline-Explained.md create mode 100644 docs/02.Tutorials/2.4.Resilience.md create mode 100644 docs/02.Tutorials/2.5.HTTPS-Lets-Encrypt.md create mode 100644 docs/02.Tutorials/2.6.Websocket.md create mode 100644 docs/02.Tutorials/2.7gRPC.md create mode 100644 docs/02.Tutorials/README.md create mode 100644 docs/03.Advanced-Cookbook/3.1.MQTT-Proxy.md create mode 100644 docs/03.Advanced-Cookbook/3.10.Performance.md create mode 100644 docs/03.Advanced-Cookbook/3.11.Migrate.md create mode 100644 docs/03.Advanced-Cookbook/3.2.Flash-Sale.md create mode 100644 docs/03.Advanced-Cookbook/3.3.Telegram-Translation-Bot.md create mode 100644 docs/03.Advanced-Cookbook/3.4.Canary-Release.md create mode 100644 docs/03.Advanced-Cookbook/3.5.Distributed-Tracing.md create mode 100644 docs/03.Advanced-Cookbook/3.6.Service-Proxy.md create mode 100644 docs/03.Advanced-Cookbook/3.7.WasmHost.md create mode 100644 docs/03.Advanced-Cookbook/3.8.FaaS.md create mode 100644 docs/03.Advanced-Cookbook/3.9.Workflow.md create mode 100644 docs/03.Advanced-Cookbook/README.md create mode 100644 docs/04.Cloud-Native/4.1.Kubernetes-Ingress-Controller.md create mode 100644 docs/04.Cloud-Native/4.2.Gateway-API.md create mode 100644 docs/04.Cloud-Native/4.3.EaseMesh.md create mode 100644 docs/04.Cloud-Native/README.md create mode 100644 docs/05.Administration/5.1.Cluster-Deployment.md create mode 100644 docs/05.Administration/README.md create mode 100644 docs/06.Development-for-Easegress/6.1.Developer-Guide.md create mode 100644 docs/06.Development-for-Easegress/6.2.Code.md create mode 100644 docs/06.Development-for-Easegress/6.3.Custom-Data.md create mode 100644 docs/06.Development-for-Easegress/6.4.egbuilder.md create mode 100644 docs/06.Development-for-Easegress/README.md create mode 100644 docs/07.Reference/README.md create mode 100644 docs/08.FAQ/README.md create mode 100644 docs/README.md diff --git a/docs/01.Getting-Started/1.1.Quick-Start.md b/docs/01.Getting-Started/1.1.Quick-Start.md new file mode 100644 index 0000000000..8da5d07835 --- /dev/null +++ b/docs/01.Getting-Started/1.1.Quick-Start.md @@ -0,0 +1,5 @@ +TODO: + +- mainly from [README](./../../README.md) getting started part. +- Need to be simple. +- use `egctl create httpproxy` as example. \ No newline at end of file diff --git a/docs/01.Getting-Started/1.2.Install.md b/docs/01.Getting-Started/1.2.Install.md new file mode 100644 index 0000000000..43201f4ec2 --- /dev/null +++ b/docs/01.Getting-Started/1.2.Install.md @@ -0,0 +1,3 @@ +TODO: + +- mainly from README, compile, docker, download from release \ No newline at end of file diff --git a/docs/01.Getting-Started/1.3.Concepts.md b/docs/01.Getting-Started/1.3.Concepts.md new file mode 100644 index 0000000000..6b5219424f --- /dev/null +++ b/docs/01.Getting-Started/1.3.Concepts.md @@ -0,0 +1,4 @@ +TODO: + +- introduce controllers (httpserver, pipeline, filters). Some main concepts. +- need write from scratch. \ No newline at end of file diff --git a/docs/01.Getting-Started/README.md b/docs/01.Getting-Started/README.md new file mode 100644 index 0000000000..b1487c8b1e --- /dev/null +++ b/docs/01.Getting-Started/README.md @@ -0,0 +1,5 @@ +# Getting Started + +- [Quick Start](1.1.Quick-Start.md) +- [Install](1.2.Install.md) +- [Concepts](1.3.Concepts.md) \ No newline at end of file diff --git a/docs/02.Tutorials/2.1.egctl-Usage.md b/docs/02.Tutorials/2.1.egctl-Usage.md new file mode 100644 index 0000000000..7eaebebe88 --- /dev/null +++ b/docs/02.Tutorials/2.1.egctl-Usage.md @@ -0,0 +1,3 @@ +TODO: + +from `egctl-cheat-sheet.md` \ No newline at end of file diff --git a/docs/02.Tutorials/2.2.HTTP-Proxy-Usage.md b/docs/02.Tutorials/2.2.HTTP-Proxy-Usage.md new file mode 100644 index 0000000000..80b64ef985 --- /dev/null +++ b/docs/02.Tutorials/2.2.HTTP-Proxy-Usage.md @@ -0,0 +1,5 @@ +TODO: + +- mainly about httpserver, pipeline (proxy filter). +- from controllers.md and filters.md. +- also need add more examples. \ No newline at end of file diff --git a/docs/02.Tutorials/2.3.Pipeline-Explained.md b/docs/02.Tutorials/2.3.Pipeline-Explained.md new file mode 100644 index 0000000000..e9198bd404 --- /dev/null +++ b/docs/02.Tutorials/2.3.Pipeline-Explained.md @@ -0,0 +1,6 @@ +TODO: + +- `globalfilter` (now in controllers.md) +- `load balancer` (loadbalancer.md) +- `api aggregation`(apiaggregation.md) +- need some change to original doc. \ No newline at end of file diff --git a/docs/02.Tutorials/2.4.Resilience.md b/docs/02.Tutorials/2.4.Resilience.md new file mode 100644 index 0000000000..363ec4f205 --- /dev/null +++ b/docs/02.Tutorials/2.4.Resilience.md @@ -0,0 +1,3 @@ +TODO: + +from resilience.md \ No newline at end of file diff --git a/docs/02.Tutorials/2.5.HTTPS-Lets-Encrypt.md b/docs/02.Tutorials/2.5.HTTPS-Lets-Encrypt.md new file mode 100644 index 0000000000..cad8d8132e --- /dev/null +++ b/docs/02.Tutorials/2.5.HTTPS-Lets-Encrypt.md @@ -0,0 +1,3 @@ +TODO: + +from security.md \ No newline at end of file diff --git a/docs/02.Tutorials/2.6.Websocket.md b/docs/02.Tutorials/2.6.Websocket.md new file mode 100644 index 0000000000..140842e649 --- /dev/null +++ b/docs/02.Tutorials/2.6.Websocket.md @@ -0,0 +1,3 @@ +TODO: + +from websocket.md \ No newline at end of file diff --git a/docs/02.Tutorials/2.7gRPC.md b/docs/02.Tutorials/2.7gRPC.md new file mode 100644 index 0000000000..2c89f5de0a --- /dev/null +++ b/docs/02.Tutorials/2.7gRPC.md @@ -0,0 +1,3 @@ +TODO: + +from controllers.md get grpcserver \ No newline at end of file diff --git a/docs/02.Tutorials/README.md b/docs/02.Tutorials/README.md new file mode 100644 index 0000000000..10276c7136 --- /dev/null +++ b/docs/02.Tutorials/README.md @@ -0,0 +1,9 @@ +# Tutorials + +- [egctl Usage](2.1.egctl-Usage.md) +- [HTTP Proxy Usage](2.2.HTTP-Proxy-Usage.md) +- [Pipeline Explained](2.3.Pipeline-Explained.md) +- [Advanced HTTP Proxy: Resilience](2.4.Resilience.md) +- [HTTPS & Let's Encrypt](2.5.HTTPS-Lets-Encrypt.md) +- [Websocket](2.6.Websocket.md) +- [gRPC](2.7gRPC.md) \ No newline at end of file diff --git a/docs/03.Advanced-Cookbook/3.1.MQTT-Proxy.md b/docs/03.Advanced-Cookbook/3.1.MQTT-Proxy.md new file mode 100644 index 0000000000..49629342ff --- /dev/null +++ b/docs/03.Advanced-Cookbook/3.1.MQTT-Proxy.md @@ -0,0 +1,3 @@ +TODO: + +from mqttproxy.md diff --git a/docs/03.Advanced-Cookbook/3.10.Performance.md b/docs/03.Advanced-Cookbook/3.10.Performance.md new file mode 100644 index 0000000000..518137e6e4 --- /dev/null +++ b/docs/03.Advanced-Cookbook/3.10.Performance.md @@ -0,0 +1,3 @@ +TODO: + +from performance.md \ No newline at end of file diff --git a/docs/03.Advanced-Cookbook/3.11.Migrate.md b/docs/03.Advanced-Cookbook/3.11.Migrate.md new file mode 100644 index 0000000000..3074a2a26e --- /dev/null +++ b/docs/03.Advanced-Cookbook/3.11.Migrate.md @@ -0,0 +1,3 @@ +TODO + +from migration.md \ No newline at end of file diff --git a/docs/03.Advanced-Cookbook/3.2.Flash-Sale.md b/docs/03.Advanced-Cookbook/3.2.Flash-Sale.md new file mode 100644 index 0000000000..cd3367e997 --- /dev/null +++ b/docs/03.Advanced-Cookbook/3.2.Flash-Sale.md @@ -0,0 +1,3 @@ +TODO: + +from flashsale.md diff --git a/docs/03.Advanced-Cookbook/3.3.Telegram-Translation-Bot.md b/docs/03.Advanced-Cookbook/3.3.Telegram-Translation-Bot.md new file mode 100644 index 0000000000..3a359124c4 --- /dev/null +++ b/docs/03.Advanced-Cookbook/3.3.Telegram-Translation-Bot.md @@ -0,0 +1,3 @@ +TODO: + +from telegram.md \ No newline at end of file diff --git a/docs/03.Advanced-Cookbook/3.4.Canary-Release.md b/docs/03.Advanced-Cookbook/3.4.Canary-Release.md new file mode 100644 index 0000000000..5767f68aee --- /dev/null +++ b/docs/03.Advanced-Cookbook/3.4.Canary-Release.md @@ -0,0 +1,3 @@ +TODO: + +from canary.md diff --git a/docs/03.Advanced-Cookbook/3.5.Distributed-Tracing.md b/docs/03.Advanced-Cookbook/3.5.Distributed-Tracing.md new file mode 100644 index 0000000000..3531a36991 --- /dev/null +++ b/docs/03.Advanced-Cookbook/3.5.Distributed-Tracing.md @@ -0,0 +1,3 @@ +TODO: + +from distributed.md \ No newline at end of file diff --git a/docs/03.Advanced-Cookbook/3.6.Service-Proxy.md b/docs/03.Advanced-Cookbook/3.6.Service-Proxy.md new file mode 100644 index 0000000000..862427c5fe --- /dev/null +++ b/docs/03.Advanced-Cookbook/3.6.Service-Proxy.md @@ -0,0 +1,3 @@ +TODO: + +from from serverproxy.md diff --git a/docs/03.Advanced-Cookbook/3.7.WasmHost.md b/docs/03.Advanced-Cookbook/3.7.WasmHost.md new file mode 100644 index 0000000000..1edcacab88 --- /dev/null +++ b/docs/03.Advanced-Cookbook/3.7.WasmHost.md @@ -0,0 +1,3 @@ +TODO: + +from wasmhost.md \ No newline at end of file diff --git a/docs/03.Advanced-Cookbook/3.8.FaaS.md b/docs/03.Advanced-Cookbook/3.8.FaaS.md new file mode 100644 index 0000000000..1f149c22b7 --- /dev/null +++ b/docs/03.Advanced-Cookbook/3.8.FaaS.md @@ -0,0 +1,3 @@ +TODO: + +from faas.md \ No newline at end of file diff --git a/docs/03.Advanced-Cookbook/3.9.Workflow.md b/docs/03.Advanced-Cookbook/3.9.Workflow.md new file mode 100644 index 0000000000..023fe41810 --- /dev/null +++ b/docs/03.Advanced-Cookbook/3.9.Workflow.md @@ -0,0 +1,3 @@ +TODO + +from workflow.md \ No newline at end of file diff --git a/docs/03.Advanced-Cookbook/README.md b/docs/03.Advanced-Cookbook/README.md new file mode 100644 index 0000000000..530dd9f547 --- /dev/null +++ b/docs/03.Advanced-Cookbook/README.md @@ -0,0 +1,13 @@ +# Advanced Cookbook + +- [MQTT Proxy](3.1.MQTT-Proxy.md) +- [Flash Sale](3.2.Flash-Sale.md) +- [Multiple API Orchestration](3.3.Telegram-Translation-Bot.md) +- [Canary Release](3.4.Canary-Release.md) +- [Distributed Tracing](3.5.Distributed-Tracing.md) +- [Service Proxy](3.6.Service-Proxy.md) +- [WasmHost](3.7.WasmHost.md) +- [FaaS](3.8.FaaS.md) +- [Workflow](3.9.Workflow.md) +- [Performance](3.10.Performance.md) +- [Migrate v1.x Filter To v2.x](3.11.Migrate.md) \ No newline at end of file diff --git a/docs/04.Cloud-Native/4.1.Kubernetes-Ingress-Controller.md b/docs/04.Cloud-Native/4.1.Kubernetes-Ingress-Controller.md new file mode 100644 index 0000000000..971dab6d9e --- /dev/null +++ b/docs/04.Cloud-Native/4.1.Kubernetes-Ingress-Controller.md @@ -0,0 +1,3 @@ +TODO: + +from ingress.md \ No newline at end of file diff --git a/docs/04.Cloud-Native/4.2.Gateway-API.md b/docs/04.Cloud-Native/4.2.Gateway-API.md new file mode 100644 index 0000000000..dd69cf569d --- /dev/null +++ b/docs/04.Cloud-Native/4.2.Gateway-API.md @@ -0,0 +1,3 @@ +TODO: + +gatewaycontroller.md \ No newline at end of file diff --git a/docs/04.Cloud-Native/4.3.EaseMesh.md b/docs/04.Cloud-Native/4.3.EaseMesh.md new file mode 100644 index 0000000000..2222e31660 --- /dev/null +++ b/docs/04.Cloud-Native/4.3.EaseMesh.md @@ -0,0 +1,3 @@ +TODO: + +meshcontroller from controllers.md \ No newline at end of file diff --git a/docs/04.Cloud-Native/README.md b/docs/04.Cloud-Native/README.md new file mode 100644 index 0000000000..cf15b24c08 --- /dev/null +++ b/docs/04.Cloud-Native/README.md @@ -0,0 +1,5 @@ +# Cloud Native + +- [Kubernetes Ingress Controller](4.1.Kubernetes-Ingress-Controller.md) +- [Kubernetes Gateway API](4.2.Gateway-API.md) +- [EaseMesh](4.3.EaseMesh.md) \ No newline at end of file diff --git a/docs/05.Administration/5.1.Cluster-Deployment.md b/docs/05.Administration/5.1.Cluster-Deployment.md new file mode 100644 index 0000000000..e28a41c37d --- /dev/null +++ b/docs/05.Administration/5.1.Cluster-Deployment.md @@ -0,0 +1,3 @@ +TODO: + +ClusterDeployment.md \ No newline at end of file diff --git a/docs/05.Administration/README.md b/docs/05.Administration/README.md new file mode 100644 index 0000000000..044e2736af --- /dev/null +++ b/docs/05.Administration/README.md @@ -0,0 +1,3 @@ +# Administration + +- [Cluster Deployment](5.1.Cluster-Deployment.md) \ No newline at end of file diff --git a/docs/06.Development-for-Easegress/6.1.Developer-Guide.md b/docs/06.Development-for-Easegress/6.1.Developer-Guide.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/06.Development-for-Easegress/6.2.Code.md b/docs/06.Development-for-Easegress/6.2.Code.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/06.Development-for-Easegress/6.3.Custom-Data.md b/docs/06.Development-for-Easegress/6.3.Custom-Data.md new file mode 100644 index 0000000000..6e653c5fd7 --- /dev/null +++ b/docs/06.Development-for-Easegress/6.3.Custom-Data.md @@ -0,0 +1,3 @@ +TODO: + +customdata.md diff --git a/docs/06.Development-for-Easegress/6.4.egbuilder.md b/docs/06.Development-for-Easegress/6.4.egbuilder.md new file mode 100644 index 0000000000..470de46dfa --- /dev/null +++ b/docs/06.Development-for-Easegress/6.4.egbuilder.md @@ -0,0 +1,3 @@ +TODO: + +egbuilder.md \ No newline at end of file diff --git a/docs/06.Development-for-Easegress/README.md b/docs/06.Development-for-Easegress/README.md new file mode 100644 index 0000000000..c123a5e760 --- /dev/null +++ b/docs/06.Development-for-Easegress/README.md @@ -0,0 +1,6 @@ +# Development for Easegress + +- [Developer Guide](6.1.Developer-Guide.md) +- [Source Code Introduction](6.2.Code.md) +- [Custom Data](6.3.Custom-Data.md) +- [egbuilder](6.4.egbuilder.md) \ No newline at end of file diff --git a/docs/07.Reference/README.md b/docs/07.Reference/README.md new file mode 100644 index 0000000000..a127356b69 --- /dev/null +++ b/docs/07.Reference/README.md @@ -0,0 +1,7 @@ +all other things from original doc will put into here. + +Including: + +- kernel-tuning.md +- controller reference. (need a lot of change.) +- filter reference. \ No newline at end of file diff --git a/docs/08.FAQ/README.md b/docs/08.FAQ/README.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000000..88e18265b4 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,10 @@ +# Menu + +- [Getting Started](01.Getting-Started/README.md) +- [Tutorials](02.Tutorials/README.md) +- [Advanced Cookbook](03.Advanced-Cookbook/README.md) +- [Cloud Native](04.Cloud-Native/README.md) +- [Administration](05.Administration/README.md) +- [Development for Easegress](06.Development-for-Easegress/README.md) +- [Reference](07.Reference/README.md) +- [FAQ](08.FAQ/README.md) \ No newline at end of file From b8656bd94911fcfb091baeec5db4147672ce994c Mon Sep 17 00:00:00 2001 From: chen Date: Mon, 4 Sep 2023 16:28:36 +0800 Subject: [PATCH 02/22] update --- docs/02.Tutorials/{2.7gRPC.md => 2.7.gRPC.md} | 0 docs/02.Tutorials/README.md | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename docs/02.Tutorials/{2.7gRPC.md => 2.7.gRPC.md} (100%) diff --git a/docs/02.Tutorials/2.7gRPC.md b/docs/02.Tutorials/2.7.gRPC.md similarity index 100% rename from docs/02.Tutorials/2.7gRPC.md rename to docs/02.Tutorials/2.7.gRPC.md diff --git a/docs/02.Tutorials/README.md b/docs/02.Tutorials/README.md index 10276c7136..fc14915f1b 100644 --- a/docs/02.Tutorials/README.md +++ b/docs/02.Tutorials/README.md @@ -6,4 +6,4 @@ - [Advanced HTTP Proxy: Resilience](2.4.Resilience.md) - [HTTPS & Let's Encrypt](2.5.HTTPS-Lets-Encrypt.md) - [Websocket](2.6.Websocket.md) -- [gRPC](2.7gRPC.md) \ No newline at end of file +- [gRPC](2.7.gRPC.md) \ No newline at end of file From 84f57bfaf3559df100293a1a230cadb6d6d2efd1 Mon Sep 17 00:00:00 2001 From: chen Date: Mon, 4 Sep 2023 17:30:54 +0800 Subject: [PATCH 03/22] update file name --- .../{3.1.MQTT-Proxy.md => 3.01.MQTT-Proxy.md} | 0 .../{3.2.Flash-Sale.md => 3.02.Flash-Sale.md} | 0 ...Bot.md => 3.03.Telegram-Translation-Bot.md} | 0 ...anary-Release.md => 3.04.Canary-Release.md} | 0 ...-Tracing.md => 3.05.Distributed-Tracing.md} | 0 ....Service-Proxy.md => 3.06.Service-Proxy.md} | 0 .../{3.7.WasmHost.md => 3.07.WasmHost.md} | 0 .../{3.8.FaaS.md => 3.08.FaaS.md} | 0 .../{3.9.Workflow.md => 3.09.Workflow.md} | 0 docs/03.Advanced-Cookbook/README.md | 18 +++++++++--------- 10 files changed, 9 insertions(+), 9 deletions(-) rename docs/03.Advanced-Cookbook/{3.1.MQTT-Proxy.md => 3.01.MQTT-Proxy.md} (100%) rename docs/03.Advanced-Cookbook/{3.2.Flash-Sale.md => 3.02.Flash-Sale.md} (100%) rename docs/03.Advanced-Cookbook/{3.3.Telegram-Translation-Bot.md => 3.03.Telegram-Translation-Bot.md} (100%) rename docs/03.Advanced-Cookbook/{3.4.Canary-Release.md => 3.04.Canary-Release.md} (100%) rename docs/03.Advanced-Cookbook/{3.5.Distributed-Tracing.md => 3.05.Distributed-Tracing.md} (100%) rename docs/03.Advanced-Cookbook/{3.6.Service-Proxy.md => 3.06.Service-Proxy.md} (100%) rename docs/03.Advanced-Cookbook/{3.7.WasmHost.md => 3.07.WasmHost.md} (100%) rename docs/03.Advanced-Cookbook/{3.8.FaaS.md => 3.08.FaaS.md} (100%) rename docs/03.Advanced-Cookbook/{3.9.Workflow.md => 3.09.Workflow.md} (100%) diff --git a/docs/03.Advanced-Cookbook/3.1.MQTT-Proxy.md b/docs/03.Advanced-Cookbook/3.01.MQTT-Proxy.md similarity index 100% rename from docs/03.Advanced-Cookbook/3.1.MQTT-Proxy.md rename to docs/03.Advanced-Cookbook/3.01.MQTT-Proxy.md diff --git a/docs/03.Advanced-Cookbook/3.2.Flash-Sale.md b/docs/03.Advanced-Cookbook/3.02.Flash-Sale.md similarity index 100% rename from docs/03.Advanced-Cookbook/3.2.Flash-Sale.md rename to docs/03.Advanced-Cookbook/3.02.Flash-Sale.md diff --git a/docs/03.Advanced-Cookbook/3.3.Telegram-Translation-Bot.md b/docs/03.Advanced-Cookbook/3.03.Telegram-Translation-Bot.md similarity index 100% rename from docs/03.Advanced-Cookbook/3.3.Telegram-Translation-Bot.md rename to docs/03.Advanced-Cookbook/3.03.Telegram-Translation-Bot.md diff --git a/docs/03.Advanced-Cookbook/3.4.Canary-Release.md b/docs/03.Advanced-Cookbook/3.04.Canary-Release.md similarity index 100% rename from docs/03.Advanced-Cookbook/3.4.Canary-Release.md rename to docs/03.Advanced-Cookbook/3.04.Canary-Release.md diff --git a/docs/03.Advanced-Cookbook/3.5.Distributed-Tracing.md b/docs/03.Advanced-Cookbook/3.05.Distributed-Tracing.md similarity index 100% rename from docs/03.Advanced-Cookbook/3.5.Distributed-Tracing.md rename to docs/03.Advanced-Cookbook/3.05.Distributed-Tracing.md diff --git a/docs/03.Advanced-Cookbook/3.6.Service-Proxy.md b/docs/03.Advanced-Cookbook/3.06.Service-Proxy.md similarity index 100% rename from docs/03.Advanced-Cookbook/3.6.Service-Proxy.md rename to docs/03.Advanced-Cookbook/3.06.Service-Proxy.md diff --git a/docs/03.Advanced-Cookbook/3.7.WasmHost.md b/docs/03.Advanced-Cookbook/3.07.WasmHost.md similarity index 100% rename from docs/03.Advanced-Cookbook/3.7.WasmHost.md rename to docs/03.Advanced-Cookbook/3.07.WasmHost.md diff --git a/docs/03.Advanced-Cookbook/3.8.FaaS.md b/docs/03.Advanced-Cookbook/3.08.FaaS.md similarity index 100% rename from docs/03.Advanced-Cookbook/3.8.FaaS.md rename to docs/03.Advanced-Cookbook/3.08.FaaS.md diff --git a/docs/03.Advanced-Cookbook/3.9.Workflow.md b/docs/03.Advanced-Cookbook/3.09.Workflow.md similarity index 100% rename from docs/03.Advanced-Cookbook/3.9.Workflow.md rename to docs/03.Advanced-Cookbook/3.09.Workflow.md diff --git a/docs/03.Advanced-Cookbook/README.md b/docs/03.Advanced-Cookbook/README.md index 530dd9f547..bcb3c323c8 100644 --- a/docs/03.Advanced-Cookbook/README.md +++ b/docs/03.Advanced-Cookbook/README.md @@ -1,13 +1,13 @@ # Advanced Cookbook -- [MQTT Proxy](3.1.MQTT-Proxy.md) -- [Flash Sale](3.2.Flash-Sale.md) -- [Multiple API Orchestration](3.3.Telegram-Translation-Bot.md) -- [Canary Release](3.4.Canary-Release.md) -- [Distributed Tracing](3.5.Distributed-Tracing.md) -- [Service Proxy](3.6.Service-Proxy.md) -- [WasmHost](3.7.WasmHost.md) -- [FaaS](3.8.FaaS.md) -- [Workflow](3.9.Workflow.md) +- [MQTT Proxy](3.01.MQTT-Proxy.md) +- [Flash Sale](3.02.Flash-Sale.md) +- [Multiple API Orchestration](3.03.Telegram-Translation-Bot.md) +- [Canary Release](3.04.Canary-Release.md) +- [Distributed Tracing](3.05.Distributed-Tracing.md) +- [Service Proxy](3.06.Service-Proxy.md) +- [WasmHost](3.07.WasmHost.md) +- [FaaS](3.08.FaaS.md) +- [Workflow](3.09.Workflow.md) - [Performance](3.10.Performance.md) - [Migrate v1.x Filter To v2.x](3.11.Migrate.md) \ No newline at end of file From 7285abc33a08a19a77539663eaf8d68fb2961de5 Mon Sep 17 00:00:00 2001 From: chen Date: Wed, 6 Sep 2023 17:22:55 +0800 Subject: [PATCH 04/22] udpate doc for getting started --- docs/01.Getting-Started/1.1.Quick-Start.md | 87 +++++++++++++++++++++- docs/01.Getting-Started/1.2.Install.md | 70 ++++++++++++++++- docs/01.Getting-Started/1.3.Concepts.md | 54 +++++++++++++- docs/01.Getting-Started/README.md | 6 +- 4 files changed, 205 insertions(+), 12 deletions(-) diff --git a/docs/01.Getting-Started/1.1.Quick-Start.md b/docs/01.Getting-Started/1.1.Quick-Start.md index 8da5d07835..a39b7dbf69 100644 --- a/docs/01.Getting-Started/1.1.Quick-Start.md +++ b/docs/01.Getting-Started/1.1.Quick-Start.md @@ -1,5 +1,84 @@ -TODO: +# Quick Start + +The basic usage of Easegress is to quickly set up a proxy for the backend servers. + +### Launch Easegress + +Easegress can be installed from pre-built binaries or from source. For details, see [Install](1.2.Install.md). + + +Then we can execute the server: + +```bash +$ easegress-server +2023-09-06T15:12:49.256+08:00 INFO cluster/config.go:110 config: advertise-client-urls: ... +... +``` + +By default, Easegress opens ports 2379, 2380, and 2381; however, you can modify these settings along with other arguments either in the configuration file or via command-line arguments. For a complete list of arguments, please refer to the `easegress-server --help` command. + +After launching successfully, we could check the status of the one-node cluster. + +```bash +$ egctl get member +... + +$ egctl describe member +... +``` + +### Reverse Proxy + +Assuming you have two backend HTTP services running at `127.0.0.1:9095` and `127.0.0.1:9096`, you can initiate an HTTP proxy from port 10080 to these backends using the following command: + +```bash +$ egctl create httpproxy demo --port 10080 \ + --rule="/pipeline=http://127.0.0.1:9095,http://127.0.0.1:9096" +``` + +Then try it: +```bash +$ curl -v 127.0.0.1:10080/pipeline +``` + +The request will be forwarded to either `127.0.0.1:9095/pipeline` or `127.0.0.1:9096/pipeline`, utilizing a round-robin load-balancing policy. + +### YAML Configuration + +The `egctl create httpproxy` command mentioned above is actually syntactic sugar; it creates two Easegress resources under the hood: `HTTPServer` and `Pipeline`. + +Now let's create them using yaml files: + +```bash +$ echo ' +kind: HTTPServer +name: demo +port: 10080 +https: false +rules: + - paths: + - path: /pipeline + backend: demo-0' | egctl create -f - +``` +More details about [HTTPServer](../02.Tutorials/2.2.HTTP-Proxy-Usage.md). + +```bash +$ echo ' +name: demo-0 +kind: Pipeline +flow: + - filter: proxy +filters: + - name: proxy + kind: Proxy + pools: + - servers: + - url: http://127.0.0.1:9095 + - url: http://127.0.0.1:9096 + loadBalance: + policy: roundRobin' | egctl create -f - +``` +More details about [Pipeline](../02.Tutorials/2.3.Pipeline-Explained.md). + +You can also modify, update, or delete these resources using `egctl`; for more details, refer to the [egctl usage](../02.Tutorials/2.1.egctl-Usage.md) guide. -- mainly from [README](./../../README.md) getting started part. -- Need to be simple. -- use `egctl create httpproxy` as example. \ No newline at end of file diff --git a/docs/01.Getting-Started/1.2.Install.md b/docs/01.Getting-Started/1.2.Install.md index 43201f4ec2..97465c9fda 100644 --- a/docs/01.Getting-Started/1.2.Install.md +++ b/docs/01.Getting-Started/1.2.Install.md @@ -1,3 +1,69 @@ -TODO: +# Install -- mainly from README, compile, docker, download from release \ No newline at end of file +Instructions for installing Easegress from pre-built binaries or from source. + +### Install pre-built binaries + +The easiest way to install Easegress is by using pre-built binaries: + +1. Download the appropriate compressed archive file for your platform from [Releases](https://github.com/megaease/easegress/releases). +2. Extract the contents of the archive, which will create a directory containing the binaries. +3. Add the extracted binaries to your system's path. You can either move and/or rename the binaries to a directory already in your path (such as /usr/local/bin), or add the newly created directory to your path. + +From a shell, test that Easegress is in your path: + +```bash +$ easegress-server --version +Easegress v2.6.1 +``` + +### Build from source + +1. Either download the Easegress repo as a ZIP file from [here](https://codeload.github.com/megaease/easegress/zip/refs/heads/main), or clone the repo with the following command: + ```bash + $ git clone https://github.com/megaease/easegress.git + ``` + +2. Navigate to the Easegress directory: + ```bash + $ cd easegress + ``` + +3. Execute the build script to generate the binaries, which will be placed in the `bin` directory: + ```bash + $ make + ``` + +4. Add the `bin` directory to your system's path: + ```bash + $ export PATH="$PATH:`pwd`/bin" + ``` + +5. Verify that `easegress-server` has been successfully added to your path: + ```bash + $ easegress-server --version + ``` + +> **Note**: +> +> - This repo requires Go 1.20+ compiler for the build. +> - If you need the WebAssembly feature, please run `make wasm`. + +### Install via Systemd Service + +To install the Systemd service, execute the following command: +```bash +$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/megaease/easegress/main/scripts/install.sh)" +``` + +### Install via Docker + +1. Pull the latest image from Docker Hub: + ```bash + $ docker pull megaease/easegress:latest + ``` + +2. Run the pulled image: + ```bash + $ docker run megaease/easegress + ``` diff --git a/docs/01.Getting-Started/1.3.Concepts.md b/docs/01.Getting-Started/1.3.Concepts.md index 6b5219424f..3e4a29cc31 100644 --- a/docs/01.Getting-Started/1.3.Concepts.md +++ b/docs/01.Getting-Started/1.3.Concepts.md @@ -1,4 +1,52 @@ -TODO: +# Concepts -- introduce controllers (httpserver, pipeline, filters). Some main concepts. -- need write from scratch. \ No newline at end of file +Easegress operates on the foundational concepts of controllers, pipelines, and filters. + +### Controllers + +The Easegress controller is the central unit responsible for various operations. Different controllers perform specific tasks; for example, the `HTTPServer` controller handles HTTP traffic. These controllers can be created, updated, edited, or deleted using `egctl` commands along with YAML files. + +Controllers like `ConsulServiceRegistry`, `EtcdServiceRegistry`, `EurekaServiceRegistry`, `ZookeeperServiceRegistry`, and `NacosServiceRegistry` are specialized for service registry tasks. + +Others, such as `HTTPServer` and `GRPCServer`, focus on managing network traffic. + +Still others, like `AutoCertManager` and `GlobalFilter`, are designed to handle specific business logic. + +For more information, refer to [controllers](../07.Reference/7.1.Controllers.md). + +### Pipelines + +The `Pipeline` is a built-in Easegress mechanism designed to orchestrate a sequence of filters for handling requests. It often operates in conjunction with `HTTPServer` and serves as its backend. The pipeline comprises a flow of filters executed in a defined sequence. + +A sample configuration might look like: + +```yaml +name: pipeline-demo +kind: Pipeline +flow: +- filter: validator +- filter: proxy +... + +filters: +- name: validator + kind: Validator + ... +- name: proxy + kind: Proxy + ... +... +``` + +For more information, refer to [pipeline explained](../02.Tutorials/2.3.Pipeline-Explained.md). + + +### Filters + +A Filter acts as a processor for handling requests and responses. In a pipeline, multiple filters are orchestrated to process the input sequentially, each returning a string result upon completion. Filters are primarily utilized within `Pipelines`. + + + +For instance, the `Proxy` filter serves as a gateway to backend services, while the `RateLimiter` filter enhances service reliability and availability by regulating the rate of incoming requests within a specified time frame. + +For more information, refer to [filters](../07.Reference/7.2.Filters.md). diff --git a/docs/01.Getting-Started/README.md b/docs/01.Getting-Started/README.md index b1487c8b1e..2d73a17c97 100644 --- a/docs/01.Getting-Started/README.md +++ b/docs/01.Getting-Started/README.md @@ -1,5 +1,5 @@ # Getting Started -- [Quick Start](1.1.Quick-Start.md) -- [Install](1.2.Install.md) -- [Concepts](1.3.Concepts.md) \ No newline at end of file +### [Quick Start](1.1.Quick-Start.md) +### [Install](1.2.Install.md) +### [Concepts](1.3.Concepts.md) \ No newline at end of file From e1bc474e0176dbb8e238db67f59166daf9437b32 Mon Sep 17 00:00:00 2001 From: chen Date: Thu, 7 Sep 2023 17:51:04 +0800 Subject: [PATCH 05/22] udpate doc for tutorials --- docs/02.Tutorials/2.1.egctl-Usage.md | 210 +++++++++++++++++- docs/02.Tutorials/2.2.HTTP-Proxy-Usage.md | 254 +++++++++++++++++++++- 2 files changed, 458 insertions(+), 6 deletions(-) diff --git a/docs/02.Tutorials/2.1.egctl-Usage.md b/docs/02.Tutorials/2.1.egctl-Usage.md index 7eaebebe88..6875a3613a 100644 --- a/docs/02.Tutorials/2.1.egctl-Usage.md +++ b/docs/02.Tutorials/2.1.egctl-Usage.md @@ -1,3 +1,209 @@ -TODO: +# egctl Usage -from `egctl-cheat-sheet.md` \ No newline at end of file +`egctl` is a command-line tool designed for managing Easegress resources, allowing you to create, update, edit, or delete them with ease. + +## Creating resources + +Easegress manifests are defined using YAML. They can be identified by the file extensions `.yaml` or `.yml`. You can create resources using either the `egctl create` or `egctl apply` commands. To view all available resources along with their supported actions, use the `egctl api-resources` command. + +```bash +cat globalfilter.yaml | egctl create -f - # create GlobalFilter resource from stdin +cat httpserver-new.yaml | egctl apply -f - # create HTTPServer resource from stdin + + +egctl apply -f ./pipeline-demo.yaml # create Pipeline resource +egctl create -f ./httpserver-demo.yaml # create HTTPServer resource + +egctl apply -f ./cdk-demo.yaml # create CustomDataKind resource +egctl create -f ./custom-data-demo.yaml # create CustomData resource +``` + +## Create HTTPProxy +`egctl create httpproxy` is used to create `HTTPServer` and corresponding `Pipelines` quickly. + +```bash +egctl create httpproxy NAME --port PORT \ + --rule HOST/PATH=ENDPOINT1,ENDPOINT2 \ + [--rule HOST/PATH=ENDPOINT1,ENDPOINT2] \ + [--tls] \ + [--auto-cert] \ + [--ca-cert-file CA_CERT_FILE] \ + [--cert-file CERT_FILE] \ + [--key-file KEY_FILE] +``` + +For example: + +```bash +# Create a HTTPServer (with port 10080) and corresponding Pipelines to direct +# request with path "/bar" to "http://127.0.0.1:8080" and "http://127.0.0.1:8081" and +# request with path "/foo" to "http://127.0.0.1:8082". +egctl create httpproxy demo --port 10080 \ + --rule="/bar=http://127.0.0.1:8080,http://127.0.0.1:8081" \ + --rule="/foo=http://127.0.0.1:8082" +``` + +this equals to +```yaml +kind: HTTPServer +name: demo +port: 10080 +https: false +rules: + - paths: + - path: /bar + backend: demo-0 + - path: /foo + backend: demo-1 + +--- +name: demo-0 +kind: Pipeline +filters: + - name: proxy + kind: Proxy + pools: + - servers: + - url: http://127.0.0.1:8080 + - url: http://127.0.0.1:8081 + loadBalance: + policy: roundRobin + +--- +name: demo-1 +kind: Pipeline +filters: + - name: proxy + kind: Proxy + pools: + - servers: + - url: http://127.0.0.1:8082 + loadBalance: + policy: roundRobin +``` + +## Viewing and finding resources + +```bash +egctl get all # view all resources +egctl get httpserver httpserver-demo # find HTTPServer resources with name "httpserver-demo" + +egctl get member # view all easegress nodes +egctl get member eg-default-name # find easegress node with name "eg-default-name" + +egctl get customdatakind # view all CustomDataKind resources +egctl get customdata cdk-demo # find CustomDataKind resource with name "cdk-demo" + +egctl describe httpserver # describe all HTTPServer resource +egctl describe pipeline pipeline-demo # describe Pipeline resource with name "pipeline-demo" +``` + +## Updating resources +```bash +egctl apply -f httpserver-demo-version2.yaml # update HTTPServer resource +egctl apply -f cdk-demo2.yaml # udpate CustomDataKind resource +``` + +## Editing resources +```bash +egctl edit httpserver httpserver-demo # edit httpserver with name httpserver-demo +egctl edit customdata cdk-demo # batch edit custom data with kind cdk-demo +egctl edit customdata cdk-demo data1 # edit custom data data1 of kind cdk-demo +``` +The default editor for `egctl edit` is `vi`. To change it, update the `EGCTL_EDITOR` environment variable. + +## Deleting resources +```bash +egctl delete httpserver httpserver-demo # delete HTTPServer resource with name "httpserver-demo" +egctl delete httpserver --all # delete all HTTPServer resources +egctl delete customdatakind cdk-demo cdk-kind # delete CustomDataKind resources named "cdk-demo" and "cdk-kind" +``` + +## Other commands +```bash +egctl logs # print easegress-server logs +egctl logs --tail 100 # print most recent 100 logs +egctl logs -f # print logs as stream + +egctl api-resources # view all available resources +egctl completion zsh # generate completion script for zsh +egctl health # check easegress health + +egctl profile info # show location of profile files +egctl profile start cpu ./cpu-profile # start the CPU profile and store the output in the ./cpu-profile file +egctl profile stop # stop profile +``` + +## Config + +By default, `egctl` searches for a file named `.egctlrc` in the `$HOME` directory. Here's an example of a `.egctlrc` file. + +```yaml +kind: Config + +# current used context. +current-context: context-default + +# "contexts" section contains "user" and "cluster" information, which informs egctl about which "user" should be used to access a specific "cluster". +contexts: + - context: + cluster: cluster-default + user: user-default + name: context-default + +# "clusters" section contains information about the "cluster". +# "server" specifies the host address that egctl should access. +# "certificate-authority" or "certificate-authority-data" contain the root certificate authority that the client uses to verify server certificates. +clusters: + - cluster: + server: localhost:2381 + certificate-authority: "/tmp/certs/ca.crt" + certificate-authority-data: "xxxx" + name: cluster-default + +# "users" section contains "user" information. +# "username" and "password" are used for basic authentication. +# either the pair ("client-key", "client-certificate") or the pair ("client-key-data", "client-certificate-data") contains the client certificate. +users: + - name: user-default + user: + username: user123 + password: password + client-certificate: "/tmp/certs/client.crt" + client-key: "/tmp/certs/client.key" +``` + +To utilize basic authentication with `username` and `password`, add the following segment to the Easegress configuration YAML file: + +```yaml +name: easegress-1 +cluster: + listen-peer-urls: + - http://localhost:2380 + ... +... +basic-auth: + user123: password + admin: admin + username: password +``` + +To verify the client certificate, incorporate the following section into the Easegress configuration YAML file: + +```yaml +name: easegress-1 +cluster: + listen-peer-urls: + - http://localhost:2380 + ... +... +# providing a non-empty client-ca-file will force the server to verify the client certificate. +client-ca-file: "/tmp/certs/ca.crt" +``` + +```bash +egctl config current-context # display the current context in use by egctl +egctl config get-contexts # view all available contexts +egctl config use-context # update the current-context field in the .egctlrc file to +egctl config view # display the contents of the configuration file +``` diff --git a/docs/02.Tutorials/2.2.HTTP-Proxy-Usage.md b/docs/02.Tutorials/2.2.HTTP-Proxy-Usage.md index 80b64ef985..0ee5563b39 100644 --- a/docs/02.Tutorials/2.2.HTTP-Proxy-Usage.md +++ b/docs/02.Tutorials/2.2.HTTP-Proxy-Usage.md @@ -1,5 +1,251 @@ -TODO: +# HTTP Proxy Usage -- mainly about httpserver, pipeline (proxy filter). -- from controllers.md and filters.md. -- also need add more examples. \ No newline at end of file +Creating an HTTP Proxy allows for traffic routing and management. You can set one up using the `egctl create httpproxy` command. For comprehensive instructions, refer to [egctl create httpproxy](2.1.egctl-Usage.md#create-httpproxy). For more advanced features, you might consider setting up `HTTPServer` and `Pipelines`. + +### HTTPServer Overview + +An `HTTPServer` acts as a listening server on a specified port, directing traffic to designated `Pipelines`. Below is a basic configuration example: + +```yaml +name: demo +kind: HTTPServer + +port: 10080 # Listening port +http3: false # HTTP3 usage; default is false +keepAlive: true # Keep the connection alive; default is true + +# Use HTTPS; default is false. +# If true, set autocert or provide certs and keys. +https: false + +# Automated certificate management. +# More info in AutoCertManager +autoCert: false + +# Public keys in PEM base64 format +certs: + cert1: + cert2: +# Corresponding private keys +keys: + cert1: + cert2: + +# Max request body size; default is 4MB +clientMaxBodySize: 4 * 1024 * 1024 + +# Max connections with clients, default 10240 +maxConnections: 10240 + +# Root certificate authorities +caCertBase64: + +# Special pipeline that can be executed +# before or/and after all pipelines in a serve. +globalFilter: global-filter-name + +# Distributed tracing settings +tracing: {} + +# IP Filter for all traffic under the server +ipFilter: + blockIPs: [] + allowIPs: [] + blockByDefault: false + +# Traffic routing rules +rules: + - host: + paths: + - path: /pipeline + backend: demo-0 +``` + +You can create, update, edit, or delete `HTTPServer` with `egctl` commands. More details in [egctl](2.1.egctl-Usage.md). + +For managing HTTPServer (create, update, edit, delete, check), use `egctl` commands. Further details can be found in the [egctl](2.1.egctl-Usage.md) section. + +For a deep dive into available parameters, consult: +- [HTTPServer](../07.Reference/7.1.Controllers.md#httpserver) +- [AutoCertManager](../07.Reference/7.1.Controllers.md#autocertmanager) +- [GlobalFilter](../07.Reference/7.1.Controllers.md#globalfilter) +- [Distributed Tracing](../03.Advanced-Cookbook/3.05.Distributed-Tracing.md) + +### Exploring Rules in Depth + +Within an HTTPServer configuration, the rules section enables precise traffic routing based on various conditions, such as host names, IP addresses, paths, methods, headers, and query parameters. + +Here's a more concise and structured breakdown of the rules section: + +```yaml +name: demo +kind: HTTPServer +port: 10080 +... + +rules: +# Rules for host matching. +# If not match, HTTPServer will check next rule. +- host: + hosts: [, ] + hostRegexp: + + # IP-based filtering. + ipFilter: {} + + # Path-based routing. + paths: + - path: /abc # Exact path match + backend: abc-pipeline + + - pathPrefix: /xyz # Matches any path with the given prefix + backend: xyz-pipeline + + - path: /efg + methods: [PUT] # Path and HTTP method condition + backend: efg-pipeline + + - pathRegexp: /[a-z]+ # Path and HTTP method condition + backend: az-pipeline + + # for path, rewriteTarget replace original path + # for pathPrefix, rewriteTarget replace prefix + # for pathRegexp, rewriteTarget use re.ReplaceAllString(path, rewriteTarget) + - path: /hig + backend: newhig-pipeline + rewriteTarget: /newhig + + # Header-based routing. + - path: /headers + headers: + - key: "X-Test" + values: [test1, test2] # Matches if any of these values are found + - key: "AllMatch" + regexp: "^true$" # Matches the exact regular expression + matchAllHeader: false # If true, all header conditions must match + backend: headers-pipeline + + # Query parameter-based routing. + - path: /query + matchAllQuery: false # If true, all query conditions must match + queries: + - key: "q" + values: ["v1", "v2"] + - key: "q2" + values: ["v3", "v4"] + backend: query-pipeline + +# next rule +- host: ... + ... +``` + +#### Order Matters: + +- Rules are checked sequentially. Place frequently matched rules at the top to optimize performance. +- Similarly, within each rule, path conditions are also checked in order. Prioritize commonly used paths. + +By carefully structuring the `rules` section, you can ensure optimal and precise traffic management within Easegress. + +#### Examples: + +With the provided configuration, + +#### 1. Path Routing + +When a request is made to `http://127.0.0.1:10080/abc`, it gets directed to the `abc-pipeline`` based on the path-based routing rule: + +```yaml +- path: /abc + backend: abc-pipeline +``` + +#### 2. Path Prefix Routing + +A request to any URL with the prefix `/xyz`, like `http://127.0.0.1:10080/xyz123`, will be directed to `xyz-pipeline` due to: + +```yaml +- pathPrefix: /xyz + backend: xyz-pipeline +``` + +#### 3. Method-based Routing + +Only `PUT` requests to `http://127.0.0.1:10080/efg` will be routed to `efg-pipeline`: + +```yaml +- path: /efg + methods: [PUT] + backend: efg-pipeline +``` + +#### 4. Regular Expression Routing + +Requests with paths that match the regular expression, like `http://127.0.0.1:10080/abcd`, get routed to `az-pipeline`: + +```yaml +- pathRegexp: /[a-z]+ + backend: az-pipeline +``` + +#### 5. Header-based Routing: +If a request has the header `X-Test` with values `test1` or `test2`, it will be directed to headers-pipeline. For instance, a request to `http://127.0.0.1:10080/headers` with the header `X-Test`: `test1` will match: + + +```yaml +- path: /headers + headers: + - key: "X-Test" + values: [test1, test2] + backend: headers-pipeline +``` + +#### 6. Query Parameter Routing + +A request to `http://127.0.0.1:10080/query?q=v1` matches the condition and will be routed to `query-pipeline`: + +```yaml +- path: /query + queries: + - key: "q" + values: ["v1", "v2"] + backend: query-pipeline +``` + +### Pipelines + +In Easegress, Pipelines are fundamental constructs that enable the arrangement and execution of filters in a sequence to process incoming requests. Think of it as a production line in a factory; each filter can alter or process the request in a specific way before passing it to the next filter (or the final destination). + +Here's a simple pipeline configuration: + +```yaml +name: pipeline-demo +kind: Pipeline +flow: + - filter: proxy +filters: + - name: proxy + kind: Proxy + pools: + - servers: + - url: http://127.0.0.1:9095 + - url: http://127.0.0.1:9096 + loadBalance: + policy: roundRobin +``` + +Explanation: + +- `flow`: Defines the order in which filters are executed. In this case, we only have one filter named "proxy". +- `filters`: Contains configurations for each filter. Here, we have: + - `name`: The filter's identifier. + - `kind`: The type of filter, which is "Proxy" in this case. + - other spec details: The configuration details and settings will differ and be specific to the chosen filter type. This means each filter has its unique parameters and settings based on its functionality and role. + +In this example, incoming requests to the pipeline are simply proxied to either `http://127.0.0.1:9095` or `http://127.0.0.1:9096` in a `round-robin` fashion. + + +Further Reading: +- `Pipeline Details`: To delve deeper into how pipelines work, their capabilities, and various configurations, consult [pipeline explained](2.3.Pipeline-Explained.md). +- `Proxy Filter`: For specifics on the Proxy filter, its functionalities, and configurations, refer to [Proxy](../07.Reference/7.2.Filters.md#proxy). +- `Other Filters`: Easegress offers a multitude of filters for various purposes. To explore them, visit [Filters](../07.Reference/7.2.Filters.md). From cdb9e2312b4b7128e0b9d5db6ad575d2eb3cf099 Mon Sep 17 00:00:00 2001 From: chen Date: Thu, 7 Sep 2023 18:15:19 +0800 Subject: [PATCH 06/22] udpate doc for tutorials --- docs/02.Tutorials/2.3.Pipeline-Explained.md | 596 +++++++++++++++++++- docs/02.Tutorials/2.4.Resilience.md | 378 ++++++++++++- 2 files changed, 967 insertions(+), 7 deletions(-) diff --git a/docs/02.Tutorials/2.3.Pipeline-Explained.md b/docs/02.Tutorials/2.3.Pipeline-Explained.md index e9198bd404..60d0984353 100644 --- a/docs/02.Tutorials/2.3.Pipeline-Explained.md +++ b/docs/02.Tutorials/2.3.Pipeline-Explained.md @@ -1,6 +1,592 @@ -TODO: +# Pipeline Explained -- `globalfilter` (now in controllers.md) -- `load balancer` (loadbalancer.md) -- `api aggregation`(apiaggregation.md) -- need some change to original doc. \ No newline at end of file +Easegress offers a rich set of filters tailored for diverse use cases, including validation, request building, and more. At its core, the `Pipeline` serves as an integrated framework within Easegress, designed specifically to coordinate and sequence filters for processing requests. + +* It supports calling multiple backend services for one input request. +* It supports conditional forward direction jumping. +* The `HTTPServer` receives incoming traffic and routes to one dedicated `Pipeline` in Easegress according to the HTTP header, path matching, etc. + + +- [Details](#details) + - [Sequences executing](#sequences-executing) + - [JumpIf](#jumpif) + - [Built-in Filter `END`](#built-in-filter-end) + - [Alias](#alias) + - [Namespace](#namespace) +- [Usage](#usage) + - [GlobalFilter](#globalfilter) + - [Load Balancer](#load-balancer) + - [Traffic Adaptor: Change Something of Two-Way Traffic](#traffic-adaptor-change-something-of-two-way-traffic) + - [API Aggregation](#api-aggregation) + - [Example 1: Simple aggregation](#example-1-simple-aggregation) + - [Example 2: Merge response body](#example-2-merge-response-body) + - [Example 3: Handle Failures](#example-3-handle-failures) + - [References](#references) + +## Details +### Sequences executing + +* The basic model of Pipeline execution is a sequence. Filters are executed one by one in the order described by the `flow` field in Pipeline's spec. + +``` + Request + │ +┌──────────┼──────────┐ +│ Pipeline │ │ +│ │ │ +│ ┌───────┴───────┐ │ +│ │requestAdaptor │ │ +│ └───────┬───────┘ │ +│ │ │ +│ │ │ +│ ┌───────▼───────┐ │ +│ │ Proxy │ │ +│ └───────┬───────┘ │ +│ │ │ +└──────────┼──────────┘ + │ + ▼ + Response +``` + +```bash +$ echo ' +name: pipeline-demo +kind: Pipeline +flow: +- filter: requestAdaptor +- filter: proxy +filters: +- name: requestAdaptor + kind: RequestAdaptor + header: + set: + X-Adapt-Key: goodplan +- name: proxy + kind: Proxy + pools: + - servers: + - url: http://127.0.0.1:9095 +' | egctl create -f - +``` +* The `pipeline-demo` above will execute the `requestAdaptor` filter first, then `proxy`. So the `proxy` filter can forward the request with the header `X-Adapt-Key` which was set by the `requestAdaptor`. + +### JumpIf + +* Easegress' filter returns a string message as the execution result. If it's empty, that means these filters handle the request without failure. Otherwise, Easegress will use these no-empty result strings as the basis for the `JumpIf` mechanism. +* The Pipeline supports `JumpIf` mechanism[2]. We use it to avoid some chosen filters' execution when something goes wrong. + +``` + Request + │ + │ +┌──────────────┼─────────────────┐ +│ Pipeline │ │ +│ ▼ │ +│ ┌──────────────────┐ │ +│ │ Validator ├────┐ │ +│ └────────┬─────────┘ │ │ +│ │ │ │ +│ ▼ │ │ +│ ┌──────────────────┐ │ │ +│ │ RequestAdaptor │ │ │ +│ └────────┬─────────┘ │ │ +│ │ Invalid│ +│ ▼ │ │ +│ ┌──────────────────┐ │ │ +│ │ Proxy │ │ │ +│ └────────┬─────────┘ │ │ +│ │ │ │ +│ ◄──────────────┘ │ +└──────────────┼─────────────────┘ + │ + ▼ + Response +``` + +```bash +$ echo ' +name: pipeline-demo +kind: Pipeline + +flow: +- filter: validator + jumpIf: { invalid: END } +- filter: requestAdaptor +- filter: proxy + +filters: +- name: validator + kind: Validator + headers: + Content-Type: + values: + - application/json +- name: requestAdaptor + kind: RequestAdaptor + header: + set: + X-Adapt-Key: goodplan +- name: proxy + kind: Proxy + pools: + - servers: + - url: http://127.0.0.1:9095 +' | egctl apply -f - +``` + +* As we can see above, `pipeline-demo` will jump to the end of pipeline execution when `validator`'s execution result is `invalid`. + +### Built-in Filter `END` + +* `END` is a built-in filter, that's we can use without defining it. From the above example, we already see that `END` can be used as the target of `jumpIf`, and we can also use it directly in the flow. + +```bash +$ echo ' +name: pipeline-demo +kind: Pipeline + +flow: +- filter: validator + jumpIf: { invalid: buildFailureResponse } +- filter: requestAdaptor +- filter: proxy +- filter: END +- filter: buildFailureResponse + +filters: +- name: validator + kind: Validator + headers: + Content-Type: + values: + - application/json +- name: requestAdaptor + kind: RequestAdaptor + header: + set: + X-Adapt-Key: goodplan +- name: proxy + kind: Proxy + pools: + - servers: + - url: http://127.0.0.1:9095 +- name: responseBuilder + kind: ResponseBuilder + template: | + statusCode: 400 + body: the request is invalid. +' | egctl apply -f - +``` + +* By using the `END` filter, we are now possible to build a custom failure response. + +### Alias + +* We have assigned a name to a filter when we define it, but a filter can be used more than once in the flow, in this case, we can assign an alias to each appearance. + + +```bash +$ echo ' +name: pipeline-demo +kind: Pipeline + +flow: +- filter: validator + jumpIf: + invalid: mockRequestForProxy2 +- filter: mockRequest +- filter: proxy1 +- filter: END +- filter: mockRequest + alias: mockRequestForProxy2 +- filter: proxy2 + +filters: +- name: validator + kind: Validator + headers: + Content-Type: + values: + - application/json +- name: mockRequest + kind: RequestBuilder + template: | + method: GET + url: /hello +- name: proxy1 + kind: Proxy + pools: + - servers: + - url: http://127.0.0.1:9095 +- name: proxy2 + kind: Proxy + pools: + - servers: + - url: http://127.0.0.2:9095 +' | egctl apply -f - +``` + +### Namespace + +* The pipeline can handle multiple requests/responses, requests/responses are grouped by namespaces, and each namespace can have at most one request and one response. +* We can set the `namespace` attribute of filters to let the filter know which request/response it should use. +* `RequestBuilder` and `ResponseBuilder` can access all requests and responses, they output the result request/response to the namespace they are assigned. +* The default namespace is `DEFAULT`. + +``` bash +echo ' +name: pipeline-api +kind: Pipeline + +flow: +- filter: copyRequest + namespace: demo1 +- filter: copyRequest + namespace: demo2 +- filter: copyRequest + namespace: demo3 +- filter: proxy-demo1 + namespace: demo1 +- filter: proxy-demo2 + namespace: demo2 +- filter: proxy-demo3 + namespace: demo3 +- filter: buildResponse + +filters: +- name: copyRequest + kind: RequestBuilder + sourceNamespace: DEFAULT +- name: proxy-demo1 + kind: Proxy + pools: + - servers: + - url: https://demo1 +- name: proxy-demo2 + kind: Proxy + pools: + - servers: + - url: https://demo2 +- name: proxy-demo3 + kind: Proxy + pools: + - servers: + - url: https://demo3 +- name: buildResponse + kind: ResponseBuilder + template: | + statusCode: 200 + body: [{{.responses.demo1.Body}}, {{.response.demo2.Body}}, {{.response.demo3.Body}}] +' | egctl create -f - +``` + +## Usage + +### GlobalFilter +GlobalFilter is a special pipeline that can be executed before or/and after all pipelines in a server. For example: + +```yaml +name: globalFilter-example +kind: GlobalFilter +beforePipeline: + flow: + - filter: validator + + filters: + - name: validator + kind: Validator + ... + +afterPipeline: + ... + +--- +name: server-example +kind: HTTPServer +globalFilter: globalFilter-example +... +``` +In this case, all requests in `HTTPServer` `server-example` go through `GlobalFilter` `globalFilter-example` before and after executing any other pipelines. + +### Load Balancer + +The reverse proxy is the common middleware that is accessed by clients, forwards them to backend servers. The reverse proxy is a very core role played by Easegress. + +The filter `Proxy` is the filter to fire requests to backend servers. It contains servers group under load balance, whose policy support `roundRobin`, `random`, `weightedRandom`, `ipHash`, `headerHash`. + +```yaml +name: pipeline-reverse-proxy +kind: Pipeline +flow: + - filter: proxy +filters: + - name: proxy + kind: Proxy + pools: + - servers: + - url: http://127.0.0.1:9095 + - url: http://127.0.0.1:9096 + - url: http://127.0.0.1:9097 + loadBalance: + policy: roundRobin +``` + +#### Traffic Adaptor: Change Something of Two-Way Traffic + +Sometimes backend applications can't adapt to quick changes of requirements of traffic. Easegress could be an adaptor between new traffic and old applications. There are 2 phases of adaption in reverse proxy: request adaption, response adaption. `RequestAdaptor` supports the adaption of a method, path, header, and body. `ResponseAdaptor` supports the adaption of header and body. As you can see, the flow in spec plays a critical role. + +```yaml +name: pipeline-reverse-proxy +kind: Pipeline +flow: + - filter: requestAdaptor + - filter: proxy + - filter: responseAdaptor +filters: + - name: requestAdaptor + kind: RequestAdaptor + host: easegress.megaease.com + method: POST + path: + trimPrefix: /apis/v2 + header: + set: + X-Api-Version: v2 + + - name: responseAdaptor + kind: ResponseAdaptor + header: + set: + Server: Easegress v1.0.0 + add: + X-Easegress-Pipeline: pipeline-reverse-proxy + + - name: proxy + kind: Proxy + pools: + - servers: + - url: http://127.0.0.1:9095 + - url: http://127.0.0.1:9096 + - url: http://127.0.0.1:9097 + loadBalance: + policy: roundRobin +``` +### API Aggregation + +API aggregation is a pattern to aggregate multiple individual requests into a single request. This pattern is useful when a client must make multiple calls to different backend systems to operate. Easegress provides filters [RequestBuilder](../07.Reference/7.2.Filters.md#requestbuilder) & [ResponseBuilder](../07.Reference/7.2.Filters.md#responsebuilder) for this powerful feature. + +#### Example 1: Simple aggregation + +1. Suppose we have three backend services called `demo1`, `demo2`, and `demo3`. + We want to call these three services and combine their response to one. + `demo1` returns `{"mega":"ease"}` as the HTTP response body, `demo2` + returns `{"hello":"world"}`, and `demo3` returns `{"hello":"new world"}`. + +``` bash +echo ' +name: pipeline-api +kind: Pipeline +flow: +- filter: copyRequest + namespace: demo1 +- filter: copyRequest + namespace: demo2 +- filter: copyRequest + namespace: demo3 +- filter: proxy-demo1 + namespace: demo1 +- filter: proxy-demo2 + namespace: demo2 +- filter: proxy-demo3 + namespace: demo3 +- filter: buildResponse + +filters: +- name: copyRequest + kind: RequestBuilder + sourceNamespace: DEFAULT +- name: proxy-demo1 + kind: Proxy + pools: + - servers: + - url: https://demo1 +- name: proxy-demo2 + kind: Proxy + pools: + - servers: + - url: https://demo2 +- name: proxy-demo3 + kind: Proxy + pools: + - servers: + - url: https://demo3 +- name: buildResponse + kind: ResponseBuilder + template: | + statusCode: 200 + body: | + [{{.responses.demo1.Body}}, {{.responses.demo2.Body}}, {{.responses.demo3.Body}}] +' | egctl create -f - +``` + +2. Creating an HTTPServer for forwarding the traffic to this pipeline. + +``` bash +echo ' +kind: HTTPServer +name: server-demo +port: 10080 +keepAlive: true +https: false +rules: + - paths: + - pathPrefix: /api + backend: pipeline-api ' | egctl create -f - +``` + +3. Send a request to the pipeline + +``` bash + +$ curl -X GET http://127.0.0.1:10080/api -v +[{"hello":"world"},{"hello":"new world"},{"mega":"ease"}] + +``` + +#### Example 2: Merge response body + +* In #Example 1, `demo2` and `demo3`'s responses share the same JSON key, + we want to merge their response body by the JSON key together. If there + are duplicated keys, we will use the last value. + +1. Update the pipeline spec + +``` bash +echo ' +name: pipeline-api +kind: Pipeline +flow: +- filter: copyRequest + namespace: demo1 +- filter: copyRequest + namespace: demo2 +- filter: copyRequest + namespace: demo3 +- filter: proxy-demo1 + namespace: demo1 +- filter: proxy-demo2 + namespace: demo2 +- filter: proxy-demo3 + namespace: demo3 +- filter: buildResponse + +filters: +- name: copyRequest + kind: RequestBuilder + sourceNamespace: DEFAULT +- name: proxy-demo1 + kind: Proxy + pools: + - servers: + - url: https://demo1 +- name: proxy-demo2 + kind: Proxy + pools: + - servers: + - url: https://demo2 +- name: proxy-demo3 + kind: Proxy + pools: + - servers: + - url: https://demo3 +- name: buildResponse + kind: ResponseBuilder + template: | + statusCode: 200 + body: | + {{mergeObject .responses.demo1.JSONBody .responses.demo2.JSONBody .responses.demo3.JSONBody | toRawJson}} +' | egctl apply -f - +``` + +2. Send a request to the pipeline + +``` bash + +$ curl -X GET http://127.0.0.1:10080/api -v +{"hello":"new world","mega":"ease"} + +``` + +#### Example 3: Handle Failures + +In #Example 1, if one of the backend services encounters a failure, the pipeline +will result a wrong result. We can improve the pipeline to handle this kind of +failures. + +1. Update the pipeline spec + +``` bash +echo ' +name: pipeline-api +kind: Pipeline +flow: +- filter: copyRequest + namespace: demo1 +- filter: copyRequest + namespace: demo2 +- filter: copyRequest + namespace: demo3 +- filter: proxy-demo1 + namespace: demo1 + jumpIf: { serverError: proxy-demo2 } +- filter: proxy-demo2 + namespace: demo2 +- filter: proxy-demo3 + namespace: demo3 +- filter: buildResponse + +filters: +- name: copyRequest + kind: RequestBuilder + sourceNamespace: DEFAULT +- name: proxy-demo1 + kind: Proxy + pools: + - servers: + - url: https://demo1 +- name: proxy-demo2 + kind: Proxy + pools: + - servers: + - url: https://demo2 +- name: proxy-demo3 + kind: Proxy + pools: + - servers: + - url: https://demo3 +- name: buildResponse + kind: ResponseBuilder + template: | + statusCode: {{$code := 503}}{{range $resp := .responses}}{{if eq $resp.StatusCode 200}}{{$code = 200}}{{break}}{{end}}{{end}}{{$code}} + body: | + {{$first := true -}} + [{{range $resp := .responses -}} + {{if eq $resp.StatusCode 200 -}} + {{if not $first}},{{end}}{{$resp.Body}}{{$first = false -}} + {{- end}} + {{- end}}] +' | egctl apply -f - +``` + +2. Stop service `demo1` and send a request to the pipeline + +``` bash +$ curl -X GET http://127.0.0.1:10080/api -v +[{"hello":"world"},{"hello":"new world"}] + +``` + +### References +1. https://en.wikipedia.org/wiki/Chain-of-responsibility_pattern +2. https://github.com/megaease/easegress/blob/main/doc/developer-guide.md#jumpif-mechanism-in-pipeline \ No newline at end of file diff --git a/docs/02.Tutorials/2.4.Resilience.md b/docs/02.Tutorials/2.4.Resilience.md index 363ec4f205..5b165834b9 100644 --- a/docs/02.Tutorials/2.4.Resilience.md +++ b/docs/02.Tutorials/2.4.Resilience.md @@ -1,3 +1,377 @@ -TODO: +# Resilience -from resilience.md \ No newline at end of file +- [Basic: Load Balance](#basic-load-balance) +- [More Livingness: Resilience of Service](#more-livingness-resilience-of-service) + - [CircuitBreaker](#circuitbreaker) + - [RateLimiter](#ratelimiter) + - [Retry](#retry) + - [TimeLimiter](#timelimiter) +- [References](#references) + - [CircuitBreaker](#circuitbreaker-1) + - [RateLimiter](#ratelimiter-1) + - [Retry](#retry-1) + - [Concepts](#concepts) + +As a Cloud Native traffic orchestrator, Easegress supports build-in resilience +features. It is the ability of your system to react to failure and still +remain functional. It's not about avoiding failure, but accepting failure +and constructing your cloud-native services to respond to it. You want to +return to a fully functioning state quickly as possible.[1] + +## Basic: Load Balance + +```yaml +name: pipeline-reverse-proxy +kind: Pipeline + +flow: +- filter: proxy + +filters: +- name: proxy + kind: Proxy + pools: + - servers: + - url: http://127.0.0.1:9095 + - url: http://127.0.0.1:9096 + - url: http://127.0.0.1:9097 + loadBalance: + policy: roundRobin +``` + +## More Livingness: Resilience of Service + +### CircuitBreaker + +CircuitBreaker leverges a finite state machine to implement the processing +logic, the state machine has three states: `CLOSED`, `OPEN`, and `HALF_OPEN`. +When the state is `CLOSED`, requests pass through normally, state transits +to `OPEN` if request failure rate or slow request rate reach a configured +threshold and requests will be shor-circuited in this state. After a +configured duration, state transits from `OPEN` to `HALF_OPEN`, in which a +limited number of requests are permitted to pass through while other +requests are still short-circuited, and state transit to `CLOSED` or `OPEN` +based on the results of the permitted requests. + +When `CLOSED`, it uses a sliding window to store and aggregate the result +of recent requests, the window can either be `COUNT_BASED` or `TIME_BASED`. +The `COUNT_BASED` window aggregates the last N requests and the `TIME_BASED` +window aggregates requests in the last N seconds, where N is the window size. + +Below is an example configuration with a `COUNT_BASED` policy. `GET` request +to paths begin with `/books/` uses this policy, which short-circuits requests +if more than half of the last 100 requests failed with status code 500, 503, +or 504. + +```yaml +name: pipeline-reverse-proxy +kind: Pipeline + +flow: +- filter: proxy + +filters: +- name: proxy + kind: Proxy + pools: + - servers: + - url: http://127.0.0.1:9095 + - url: http://127.0.0.1:9096 + - url: http://127.0.0.1:9097 + loadBalance: + policy: roundRobin + circuitBreakerPolicy: countBased + failureCodes: [500, 503, 504] + +resilience: +- name: countBased + kind: CircuitBreaker + slidingWindowType: COUNT_BASED + failureRateThreshold: 50 + slidingWindowSize: 100 +``` + +And we can also use a `TIME_BASED` policy, which short-circuits requests +if more than 60% of the requests within the last 200 seconds failed. + +```yaml +resilience: +- name: time-based-policy + kind: CircuitBreaker + slidingWindowType: TIME_BASED + failureRateThreshold: 60 + slidingWindowSize: 200 +``` + +In addition to failures, we can also short-circuit slow requests. Below +configuration regards requests which cost more than 30 seconds as slow +requests and short-circuits requests if 60% of recent requests are slow. + +```yaml +resilience: +- name: countBased + kind: CircuitBreaker + slowCallRateThreshold: 60 + slowCallDurationThreshold: 30s +``` + +For a policy, if the first request fails, the failure rate could be 100% +because there's only one request. This is not the desired behavior in most +cases, we can avoid it by specifying `minimumNumberOfCalls`. + +```yaml +resilience: +- name: countBased + kind: CircuitBreaker + minimumNumberOfCalls: 10 +``` + +We can also configure the wait duration in the `open` state and the max +wait duration in the `half-open` state: + +```yaml +resilience: +- name: countBased + kind: CircuitBreaker + waitDurationInOpenState: 2m + maxWaitDurationInHalfOpenState: 1m +``` + +In the `half-open` state, we can limit the number of permitted requests: + +```yaml +resilience: +- name: countBased + kind: CircuitBreaker + permittedNumberOfCallsInHalfOpenState: 10 +``` + +For the full YAML, see [here](#circuitbreaker-1), and please refer +[CircuitBreaker Policy](../07.Reference/7.1.Controllers.md#circuitbreaker-policy) +for more information. + +### RateLimiter + +> NOTE: When there are multiple instances of Easegress, the configuration +> will be applied for every instance equally. For example, TPS of RateLimiter +> is configured with 100 in 3-instances cluster, so the total TPS will be 300. + +The below configuration limits the request rate for requests to `/admin` +and requests that match regular expression `^/pets/\d+$`. + +```yaml +name: pipeline-reverse-proxy +kind: Pipeline + +flow: +- filter: rate-limiter +- filter: proxy + +filters: +- name: rate-limiter + kind: RateLimiter + policies: + - name: policy-example + timeoutDuration: 100ms + limitRefreshPeriod: 10ms + limitForPeriod: 50 + defaultPolicyRef: policy-example + urls: + - methods: [GET, POST, PUT, DELETE] + url: + exact: /admin + regex: ^/pets/\d+$ + policyRef: policy-example +- name: proxy + kind: Proxy +``` + +For the full YAML, see [here](#ratelimiter-1). + +### Retry + +If we want to retry a failed request, for example, retry on HTTP status +codes 500, 503, and 504, we can create a `RetryerPolicy` with the below +configuration, it makes at most 3 attempts on failure. + +```yaml +name: pipeline-reverse-proxy +kind: Pipeline + +flow: +- filter: retryer +- filter: proxy + +filters: +- name: proxy + kind: Proxy + pools: + - servers: + - url: http://127.0.0.1:9095 + - url: http://127.0.0.1:9096 + - url: http://127.0.0.1:9097 + loadBalance: + policy: roundRobin + retryPolicy: retry3Times + failureCodes: [500, 503, 504] + +resilience: +- name: retry3Times + kind: Retry + maxAttempts: 3 + waitDuration: 500ms +``` + +By default, the wait duration between two attempts is `waitDuration`, but +this can be changed by specifying `backOffPolicy` and `randomizationFactor`. + +```yaml +resilience: +- name: retry3Times + kind: Retry + backOffPolicy: Exponential + randomizationFactor: 0.5 +``` + +For the full YAML, see [here](#retry-1), and please refer +[Retry Policy](../07.Reference/7.1.Controllers.md#retry-policy) for more information. + +### TimeLimiter + +TimeLimiter limits the time of requests, a request is canceled if it cannot +get a response in configured duration. As this resilience type only requires +config a timeout duration, it is implemented directly on filters like `Proxy`. + +```yaml +name: pipeline-reverse-proxy +kind: Pipeline + +flow: +- filter: retryer +- filter: proxy + +filters: +- name: proxy + kind: Proxy + pools: + - servers: + - url: http://127.0.0.1:9095 + - url: http://127.0.0.1:9096 + - url: http://127.0.0.1:9097 + loadBalance: + policy: roundRobin + timeout: 500ms +``` + +## References + +### CircuitBreaker + +```yaml +name: pipeline-reverse-proxy +kind: Pipeline + +flow: +- filter: proxy + +filters: +- name: proxy + kind: Proxy + pools: + - servers: + - url: http://127.0.0.1:9095 + - url: http://127.0.0.1:9096 + - url: http://127.0.0.1:9097 + loadBalance: + policy: roundRobin + circuitBreakerPolicy: countBasedPolicy + failureCodes: [500, 503, 504] + +resilience: +- name: countBasedPolicy + kind: CircuitBreaker + slidingWindowType: COUNT_BASED + failureRateThreshold: 50 + slidingWindowSize: 100 + slowCallRateThreshold: 60 + slowCallDurationThreshold: 30s + minimumNumberOfCalls: 10 + waitDurationInOpenState: 2m + maxWaitDurationInHalfOpenState: 1m + permittedNumberOfCallsInHalfOpenState: 10 +- name: timeBasedPolicy + kind: CircuitBreaker + slidingWindowType: TIME_BASED + failureRateThreshold: 60 + slidingWindowSize: 200 +``` + +### RateLimiter + +```yaml +name: pipeline-reverse-proxy +kind: Pipeline + +flow: + - filter: rate-limiter + - filter: proxy + +filters: +- name: rate-limiter + kind: RateLimiter + policies: + - name: policy-example + timeoutDuration: 100ms + limitRefreshPeriod: 10ms + limitForPeriod: 50 + defaultPolicyRef: policy-example + urls: + - methods: [GET, POST, PUT, DELETE] + url: + exact: /admin + regex: ^/pets/\d+$ + policyRef: policy-example +- name: proxy + kind: Proxy + pools: + - servers: + - url: http://127.0.0.1:9095 + - url: http://127.0.0.1:9096 + - url: http://127.0.0.1:9097 + loadBalance: + policy: roundRobin +``` + +### Retry + +```yaml +name: pipeline-reverse-proxy +kind: Pipeline + +flow: +- filter: proxy + +filters: +- name: proxy + kind: Proxy + pools: + - servers: + - url: http://127.0.0.1:9095 + - url: http://127.0.0.1:9096 + - url: http://127.0.0.1:9097 + loadBalance: + policy: roundRobin + retryPolicy: retry3Times + failureCodes: [500, 503, 504] + +resilience: +- name: retry3Times + kind: Retry + backOffPolicy: Exponential + randomizationFactor: 0.5 + maxAttempts: 3 + waitDuration: 500ms +``` + +### Concepts +1. https://docs.microsoft.com/en-us/dotnet/architecture/cloud-native/resiliency From 0488d147db2c38c79a5fffd2f0d0311fb0b29b76 Mon Sep 17 00:00:00 2001 From: chen Date: Fri, 8 Sep 2023 15:48:28 +0800 Subject: [PATCH 07/22] update doc for tutorials, cookbook and reference --- docs/02.Tutorials/2.1.egctl-Usage.md | 13 +- docs/02.Tutorials/2.2.HTTP-Proxy-Usage.md | 22 +- docs/02.Tutorials/2.3.Pipeline-Explained.md | 4 +- docs/02.Tutorials/2.5.HTTPS-Lets-Encrypt.md | 365 +++- docs/02.Tutorials/2.6.Websocket.md | 105 +- docs/02.Tutorials/2.7.gRPC.md | 28 +- docs/02.Tutorials/README.md | 2 +- docs/03.Advanced-Cookbook/3.01.MQTT-Proxy.md | 317 ++- docs/03.Advanced-Cookbook/3.02.Flash-Sale.md | 494 ++++- .../3.03.Multiple-API-Orchestration.md | 417 ++++ .../3.03.Telegram-Translation-Bot.md | 3 - .../3.04.Canary-Release.md | 264 ++- .../3.05.Distributed-Tracing.md | 134 +- docs/03.Advanced-Cookbook/3.07.WasmHost.md | 457 +++- docs/03.Advanced-Cookbook/README.md | 2 +- docs/07.Reference/7.1.Controllers.md | 819 ++++++++ docs/07.Reference/7.2.Filters.md | 1870 +++++++++++++++++ 17 files changed, 5286 insertions(+), 30 deletions(-) create mode 100644 docs/03.Advanced-Cookbook/3.03.Multiple-API-Orchestration.md delete mode 100644 docs/03.Advanced-Cookbook/3.03.Telegram-Translation-Bot.md create mode 100644 docs/07.Reference/7.1.Controllers.md create mode 100644 docs/07.Reference/7.2.Filters.md diff --git a/docs/02.Tutorials/2.1.egctl-Usage.md b/docs/02.Tutorials/2.1.egctl-Usage.md index 6875a3613a..96e8d1e45d 100644 --- a/docs/02.Tutorials/2.1.egctl-Usage.md +++ b/docs/02.Tutorials/2.1.egctl-Usage.md @@ -1,4 +1,13 @@ -# egctl Usage +# egctl Usage + +- [Creating resources](#creating-resources) +- [Create HTTPProxy](#create-httpproxy) +- [Viewing and finding resources](#viewing-and-finding-resources) +- [Updating resources](#updating-resources) +- [Editing resources](#editing-resources) +- [Deleting resources](#deleting-resources) +- [Other commands](#other-commands) +- [Config \& Security](#config--security) `egctl` is a command-line tool designed for managing Easegress resources, allowing you to create, update, edit, or delete them with ease. @@ -134,7 +143,7 @@ egctl profile start cpu ./cpu-profile # start the CPU profile and store the out egctl profile stop # stop profile ``` -## Config +## Config & Security By default, `egctl` searches for a file named `.egctlrc` in the `$HOME` directory. Here's an example of a `.egctlrc` file. diff --git a/docs/02.Tutorials/2.2.HTTP-Proxy-Usage.md b/docs/02.Tutorials/2.2.HTTP-Proxy-Usage.md index 0ee5563b39..fe7de173b7 100644 --- a/docs/02.Tutorials/2.2.HTTP-Proxy-Usage.md +++ b/docs/02.Tutorials/2.2.HTTP-Proxy-Usage.md @@ -1,8 +1,20 @@ -# HTTP Proxy Usage +# HTTP Proxy Usage + +- [HTTPServer](#httpserver) + - [Exploring Rules in Depth](#exploring-rules-in-depth) + - [Order Matters](#order-matters) + - [Examples](#examples) + - [1. Path Routing](#1-path-routing) + - [2. Path Prefix Routing](#2-path-prefix-routing) + - [3. Method-based Routing](#3-method-based-routing) + - [4. Regular Expression Routing](#4-regular-expression-routing) + - [5. Header-based Routing:](#5-header-based-routing) + - [6. Query Parameter Routing](#6-query-parameter-routing) +- [Pipelines](#pipelines) Creating an HTTP Proxy allows for traffic routing and management. You can set one up using the `egctl create httpproxy` command. For comprehensive instructions, refer to [egctl create httpproxy](2.1.egctl-Usage.md#create-httpproxy). For more advanced features, you might consider setting up `HTTPServer` and `Pipelines`. -### HTTPServer Overview +## HTTPServer An `HTTPServer` acts as a listening server on a specified port, directing traffic to designated `Pipelines`. Below is a basic configuration example: @@ -140,14 +152,14 @@ rules: ... ``` -#### Order Matters: +### Order Matters - Rules are checked sequentially. Place frequently matched rules at the top to optimize performance. - Similarly, within each rule, path conditions are also checked in order. Prioritize commonly used paths. By carefully structuring the `rules` section, you can ensure optimal and precise traffic management within Easegress. -#### Examples: +### Examples With the provided configuration, @@ -212,7 +224,7 @@ A request to `http://127.0.0.1:10080/query?q=v1` matches the condition and will backend: query-pipeline ``` -### Pipelines +## Pipelines In Easegress, Pipelines are fundamental constructs that enable the arrangement and execution of filters in a sequence to process incoming requests. Think of it as a production line in a factory; each filter can alter or process the request in a specific way before passing it to the next filter (or the final destination). diff --git a/docs/02.Tutorials/2.3.Pipeline-Explained.md b/docs/02.Tutorials/2.3.Pipeline-Explained.md index 60d0984353..56d67a15f4 100644 --- a/docs/02.Tutorials/2.3.Pipeline-Explained.md +++ b/docs/02.Tutorials/2.3.Pipeline-Explained.md @@ -21,7 +21,7 @@ Easegress offers a rich set of filters tailored for diverse use cases, including - [Example 1: Simple aggregation](#example-1-simple-aggregation) - [Example 2: Merge response body](#example-2-merge-response-body) - [Example 3: Handle Failures](#example-3-handle-failures) - - [References](#references) +- [References](#references) ## Details ### Sequences executing @@ -587,6 +587,6 @@ $ curl -X GET http://127.0.0.1:10080/api -v ``` -### References +## References 1. https://en.wikipedia.org/wiki/Chain-of-responsibility_pattern 2. https://github.com/megaease/easegress/blob/main/doc/developer-guide.md#jumpif-mechanism-in-pipeline \ No newline at end of file diff --git a/docs/02.Tutorials/2.5.HTTPS-Lets-Encrypt.md b/docs/02.Tutorials/2.5.HTTPS-Lets-Encrypt.md index cad8d8132e..f432a80eff 100644 --- a/docs/02.Tutorials/2.5.HTTPS-Lets-Encrypt.md +++ b/docs/02.Tutorials/2.5.HTTPS-Lets-Encrypt.md @@ -1,3 +1,364 @@ -TODO: +# Security -from security.md \ No newline at end of file +- [HTTPS](#https) +- [Let's Encrypt](#lets-encrypt) +- [Security: Verify Credential](#security-verify-credential) + - [Header](#header) + - [JWT](#jwt) + - [Signature](#signature) + - [OAuth2](#oauth2) + - [Basic Auth](#basic-auth) +- [References](#references) + - [Header](#header-1) + - [JWT](#jwt-1) + - [Signature](#signature-1) + - [OAuth2](#oauth2-1) + - [Basic Auth](#basic-auth-1) + - [Reference](#reference) + + +## HTTPS + +Implementing HTTPS in `HTTPServer` is straightforward. You either provide the required certificates and keys, or employ `AutoCertManager` to handle them using services like `Let's Encrypt`. + +To activate HTTPS in your `HTTPServer`, toggle the `https` field to `true` and offer the relevant certificates and keys: + +```yaml +name: demo +kind: HTTPServer + +# Use HTTPS; default is false. +# If true, set autocert or provide certs and keys. +https: true + +# Automated certificate management, such as Let's Encrypt. +# More info in AutoCertManager +autoCert: false + +# Public keys in PEM base64 format +certs: + cert1: + cert2: +# Corresponding private keys +keys: + cert1: + cert2: + +... +``` + + +## Let's Encrypt + +In Easegress, `AutoCertManager` automatically manage HTTPS certificates. The config looks like: + +```yaml +kind: AutoCertManager +name: autocert + +# An email address for CA account. +email: someone@megaease.com + +# The endpoint of the CA directory, not required. +# Default to use Let's Encrypt. +directoryURL: https://acme-v02.api.letsencrypt.org/directory + +# A certificate will be renewed before this duration of its expire time. +# Default 720h. +renewBefore: 720h + +# Enable HTTP-01 challenge, default true. +enableHTTP01: true + +# Enable TLS-ALPN-01 challenge, default true. +enableTLSALPN01: true + +# Enable DNS-01 challenge, default true. +enableDNS01: true + +# Domains to be managed, required. +domains: + - name: "*.megaease.com" + dnsProvider: + name: dnspod + zone: megaease.com + apiToken: +``` + +See more details about `AutoCertManager` in [here](../07.Reference/7.1.Controllers.md#autocertmanager). + +## Security: Verify Credential + +As a production-ready cloud-native traffic orchestrator, Easegress cares about security and provides several features to ensure that. + +### Header + +Using Headers validation in Easegress. This is the simplest way for validating requests. It checks the HTTP request headers by using regular expression matching. + +``` yaml +name: pipeline-reverse-proxy +kind: Pipeline +flow: + - filter: header-validator + - filter: proxy +filters: + - kind: Validator + name: header-validator + headers: + Is-Valid: + values: ["abc", "goodplan"] + regexp: "^ok-.+$" + - name: proxy + kind: Proxy + ... +``` + +- To enable Header type validator correctly in the pipeline, we should add it before filter `Proxy`. +- As the example above, it will check the `Is-Valid` header field by trying to match `abc` or `goodplan`. Also, it will use `^ok-.+$` regular expression for checking if it can't match the `values` filed. + +For the full YAML, see [here](#header-1) + +### JWT + +Using JWT validation in Easegress. JWT is wildly used in the modern web environment. JSON Web Token (JWT, pronounced /dʒɒt/, same as the word "jot") is a proposed Internet standard for creating data with optional signature and/or optional encryption whose payload holds JSON that asserts some number of claims.[1] +> Easegress supports three types of JWT, HS256, HS384, and HS512. + + +``` yaml +name: pipeline-reverse-proxy +kind: Pipeline +flow: + - filter: jwt-validator + - filter: proxy +filters: + - kind: Validator + name: jwt-validator + jwt: + cookieName: auth + algorithm: HS256 + secret: 6d79736563726574 + - name: proxy + kind: Proxy + ... +``` + +The example above will check the value named `auth` in the cookie with HS256 with the secret,6d79736563726574. + +For the full YAML, see [here](#jwt-1) + +### Signature +Using Signature validation in Easegress. Signature validation implements an Amazon Signature V4[2] compatible signature validation validator. Once you enable this kind of validation, please make sure your HTTP client has followed the signature generation process in AWS V4 doc and bring it to request Easegress. + +``` yaml +name: pipeline-reverse-proxy +kind: Pipeline +flow: + - filter: signature-validator + - filter: proxy +filters: + - kind: Validator + name: signature-validator + signature: + accessKeys: + AKID: SECRET + - name: proxy + kind: Proxy + ... +``` + +The example here only uses an `accessKeys` for processing Amazon Signature V4 validation. It also has other complicated and customized fields for more security purposes. Check it out in the Easegress filter doc if needed.[3] + +For the full YAML, see [here](#signature-1) + +### OAuth2 + +Using OAuth2 validation in Easegress. OAuth 2.0 is the industry-standard protocol for authorization. OAuth 2.0 focuses on client developer simplicity while providing specific authorization flows for web applications, desktop applications, mobile phones, and living room devices. This specification and its extensions are being developed within the IETF OAuth Working Group.[4] + +``` yaml +name: pipeline-reverse-proxy +kind: Pipeline +flow: + - filter: oauth-validator + - filter: proxy +filters: + - kind: Validator + name: oauth-validator + oauth2: + tokenIntrospect: + endPoint: https://127.0.0.1:8443/auth/realms/test/protocol/openid-connect/token/introspect + clientId: easegress + clientSecret: 42620d18-871d-465f-912a-ebcef17ecb82 + insecureTls: false + - name: proxy + kind: Proxy + ... +``` + +The example above uses a token introspection server, which is provided by `endpoint` filed for validation. It also supports `Self-Encoded Access Tokens mode` which will require a JWT related configuration included. Check it out in the Easegress filter doc if needed. [5] + +For the full YAML, see [here](#oauth-1) + +### Basic Auth + +Using Basic Auth validation in Easegress. Basic access authentication is the simplest technique for enforcing access control to web resources [6]. You can create .htpasswd file using *apache2-util* `htpasswd` [7] for storing encrypted user credentials. Please note that Basic Auth is not the most secure access control technique and it is not recommended to depend solely to Basic Auth when designing the security features of your environment. + +``` yaml +name: pipeline-reverse-proxy +kind: Pipeline +flow: + - filter: oauth-validator + - filter: proxy +filters: + - kind: Validator + name: oauth-validator + basicAuth: + mode: "FILE" + userFile: '/etc/apache2/.htpasswd' + - name: proxy + kind: Proxy + ... +``` + +The example above uses credentials defined in `/etc/apache2/.htpasswd` to restrict access. Please check out apache2-utils documentation [7] for more details. + +For the full YAML, see [here](#basic-auth-1) + +## References + +### Header + +``` yaml +name: pipeline-reverse-proxy +kind: Pipeline +flow: + - filter: header-validator + - filter: proxy +filters: + - kind: Validator + name: header-validator + headers: + Is-Valid: + values: ["abc", "goodplan"] + regexp: "^ok-.+$" + - name: proxy + kind: Proxy + pools: + - servers: + - url: http://127.0.0.1:9095 + - url: http://127.0.0.1:9096 + - url: http://127.0.0.1:9097 + loadBalance: + policy: roundRobin +``` + +### JWT + +```yaml +name: pipeline-reverse-proxy +kind: Pipeline +flow: + - filter: jwt-validator + - filter: proxy +filters: + - kind: Validator + name: jwt-validator + jwt: + cookieName: auth + algorithm: HS256 + secret: 6d7973656372657 + - kind: Proxy + name: proxy + pools: + - servers: + - url: http://127.0.0.1:9095 + - url: http://127.0.0.1:9096 + - url: http://127.0.0.1:9097 + loadBalance: + policy: roundRobin +``` + +### Signature + +```yaml +name: pipeline-reverse-proxy +kind: Pipeline +flow: + - filter: signature-validator + - filter: proxy +filters: + - kind: Validator + name: signature-validator + signature: + accessKeys: + AKID: SECRET + - kind: Proxy + pools: + - servers: + - url: http://127.0.0.1:9095 + - url: http://127.0.0.1:9096 + - url: http://127.0.0.1:9097 + loadBalance: + policy: roundRobin +``` + +### OAuth2 + +```yaml +name: pipeline-reverse-proxy +kind: Pipeline +flow: + - filter: oauth-validator + - filter: proxy +filters: + - kind: Validator + name: oauth-validator + oauth2: + tokenIntrospect: + endPoint: https://127.0.0.1:8443/auth/realms/test/protocol/openid-connect/token/introspect + clientId: easegress + clientSecret: 42620d18-871d-465f-912a-ebcef17ecb82 + insecureTls: false + - kind: Proxy + pools: + - servers: + - url: http://127.0.0.1:9095 + - url: http://127.0.0.1:9096 + - url: http://127.0.0.1:9097 + loadBalance: + policy: roundRobin +``` + +### Basic Auth + +``` yaml +name: pipeline-reverse-proxy +kind: Pipeline +flow: + - filter: header-validator + - filter: proxy +filters: + - kind: Validator + name: basic-auth-validator + basicAuth: + mode: "FILE" + userFile: '/etc/apache2/.htpasswd' + - name: proxy + kind: Proxy + pools: + - servers: + - url: http://127.0.0.1:9095 + - url: http://127.0.0.1:9096 + - url: http://127.0.0.1:9097 + loadBalance: + policy: roundRobin +``` + +### Reference + +1. https://en.wikipedia.org/wiki/JSON_Web_Token +2. https://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html +3. https://github.com/megaease/easegress/blob/main/doc/reference/filters.md#signerliteral +4. https://oauth.net/2/ +5. https://github.com/megaease/easegress/blob/main/doc/reference/filters.md#validatoroauth2jwt +6. https://en.wikipedia.org/wiki/Basic_access_authentication +7. https://manpages.debian.org/testing/apache2-utils/htpasswd.1.en.html diff --git a/docs/02.Tutorials/2.6.Websocket.md b/docs/02.Tutorials/2.6.Websocket.md index 140842e649..8cad755212 100644 --- a/docs/02.Tutorials/2.6.Websocket.md +++ b/docs/02.Tutorials/2.6.Websocket.md @@ -1,3 +1,104 @@ -TODO: +# WebSocket -from websocket.md \ No newline at end of file +- [Background](#background) +- [Design](#design) +- [Example](#example) +- [References](#references) + +## Background + +- Reverse proxy is one of most popular features of Easegress and it's suitable + for many production and development scenarios. + +- In reversed proxy use cases, `WebSocket` is a widely used protocol for a + full-duplex communication solution between client and server. WebSocket + relies on TCP.[1] + +- Many reverse proxy support `WebSocket`,e.g., NGINX[2], Traefik, and so on. + +## Design + +- The `WebSocketProxy` is a filter of Easegress, and can be put into a pipeline. +- Easegress uses `github.com/golang/x/net/websocket` to implement + `WebSocketProxy` filter. + +1. Spec + +* Pipeline with a `WebSocketProxy` filter: + +```yaml +name: websocket-pipeline +kind: Pipeline + +flow: +- filter: wsproxy + +filters: +- kind: WebSocketProxy + name: wsproxy + defaultOrigin: http://127.0.0.1/hello + pools: + - servers: + - url: ws://127.0.0.1:12345 + ``` + +* HTTPServer to route traffic to the websocket-pipeline: + +```yaml +name: demo-server +kind: HTTPServer +port: 8080 +rules: +- paths: + - path: /ws + clientMaxBodySize: -1 # REQUIRED! + backend: websocket-pipeline +``` + +Note: `clientMaxBodySize` must be `-1`. + +2. Request sequence + + ```none + +--------------+ +--------------+ +--------------+ + | | 1 | | 2 | | + | client +--------------->| Easegress +--------------->| websocket | + | |<---------------+ |<---------------+ backend | + | | 4 | | 3 | | + +--------------+ +--------------+ +--------------+ + ``` + +3. Headers + +We copy all headers from your HTTP request to websocket backend, except +ones used by websocket package to build connection. Based on [3], we also +add `X-Forwarded-For`, `X-Forwarded-Host`, `X-Forwarded-Proto` to http +headers that send to websocket backend. + +> note: websocket use `Upgrade`, `Connection`, `Sec-Websocket-Key`, + `Sec-Websocket-Version`, `Sec-Websocket-Extensions` and + `Sec-Websocket-Protocol` in http headers to set connection. + +## Example + +1. Send request + + ```bash + curl --include \ + --no-buffer \ + --header "Connection: Upgrade" \ + --header "Upgrade: websocket" \ + --header "Host: 127.0.0.1:10020" \ + --header "Sec-WebSocket-Key: your-key-here" \ + --header "Sec-WebSocket-Version: 13" \ + http://127.0.0.1:8080/ + ``` + +2. This request to Easegress will be forwarded to websocket backend + `ws://127.0.0.1:12345`. + +## References + +1. +2. +3. diff --git a/docs/02.Tutorials/2.7.gRPC.md b/docs/02.Tutorials/2.7.gRPC.md index 2c89f5de0a..dab4965cd5 100644 --- a/docs/02.Tutorials/2.7.gRPC.md +++ b/docs/02.Tutorials/2.7.gRPC.md @@ -1,3 +1,27 @@ -TODO: +# gRPC -from controllers.md get grpcserver \ No newline at end of file +#### gRRC Service Proxy + +Easegress gRPC servers is a proxy server base on gRPC protocol. + +##### Simple Proxy Configuration +Below is one of the simplest gRPC Proxy Server, and it will listen on port `8080` and handle the requests that includes kv `content-type:application/grpc` in headers by use backend `pipeline-grpc-forward` +``` yaml +name: server-grpc +kind: GRPCServer + +port: 8080 +maxConnections: 1024 +maxConnectionIdle: 60s + +ipFilter: {} + +rules: + - methods: + - backend: pipeline-grpc + headers: + - key: "Content-Type" + values: + - "application/grpc" +xForwardedFor: true +``` \ No newline at end of file diff --git a/docs/02.Tutorials/README.md b/docs/02.Tutorials/README.md index fc14915f1b..660286eecb 100644 --- a/docs/02.Tutorials/README.md +++ b/docs/02.Tutorials/README.md @@ -4,6 +4,6 @@ - [HTTP Proxy Usage](2.2.HTTP-Proxy-Usage.md) - [Pipeline Explained](2.3.Pipeline-Explained.md) - [Advanced HTTP Proxy: Resilience](2.4.Resilience.md) -- [HTTPS & Let's Encrypt](2.5.HTTPS-Lets-Encrypt.md) +- [HTTPS & Let's Encrypt & More](2.5.HTTPS-Lets-Encrypt.md) - [Websocket](2.6.Websocket.md) - [gRPC](2.7.gRPC.md) \ No newline at end of file diff --git a/docs/03.Advanced-Cookbook/3.01.MQTT-Proxy.md b/docs/03.Advanced-Cookbook/3.01.MQTT-Proxy.md index 49629342ff..ac7cb3fe06 100644 --- a/docs/03.Advanced-Cookbook/3.01.MQTT-Proxy.md +++ b/docs/03.Advanced-Cookbook/3.01.MQTT-Proxy.md @@ -1,3 +1,316 @@ -TODO: +# MQTT Proxy -from mqttproxy.md +- [MQTT Proxy](#mqtt-proxy) +- [Background](#background) +- [Design](#design) +- [Example](#example) +- [Topic Mapping](#topic-mapping) + - [Match different topic mapping policy](#match-different-topic-mapping-policy) + - [Detail of single policy](#detail-of-single-policy) +- [HTTP endpoint](#http-endpoint) +- [References](#references) + + + +# Background +- MQTT is a standard messaging protocol for IoT (Internet of Things) which is extremely lightweight and used by a wide variety of industries. +- By supporting MQTT Proxy in Easegress, MQTT clients can produce messages to backend through publish packet pipeline. +- We also provide the HTTP endpoint to allow the backend to send messages to MQTT clients. + +# Design +- Use `github.com/eclipse/paho.mqtt.golang/packets` to parse MQTT packet. `paho.mqtt.golang` is a MQTT 3.1.1 go client introduced by Eclipse Foundation (who also introduced the most widely used MQTT broker mosquitto). +- As a MQTT proxy, we support MQTT clients to `publish` messages to backend through publish packet pipeline. +- As `Pipeline` is protocol independent, it can use MQTT filters to do things like user authentication or topic mapping (map MQTT multi-level topic into single topic and key-value headers). +- We also support MQTT clients to `subscribe` topics (wildcard is supported) and send messages back to the MQTT clients through the HTTP endpoint. + +By default: +``` + publish msg publish pipeline +MQTT client ------------> Easegress MQTTProxy ----------------> Backend like Kafka + +all published msg will go to Backend, will not send to other MQTT clients. + + subscribe msg +MQTT client <---------------- Easegress MQTT HTTP Endpoint <---- Backend Server + +all msg send back to MQTT clients come from HTTP endpoint. +``` +- We assume that IoT devices (use MQTT client) report their status to the backend (through tools like Kafka), and backend process these messages and send instructions back to IoT devices. + +Using `brokerMode`: +``` + publish msg publish pipeline +MQTT client ------------> Easegress MQTTProxy ----------------> Backend like Kafka + | + | also send msg to subscribers + | + subscribe msg send msg through http endpoint +MQTT client <------------ Easegress <------------------------------- Backend Server +``` +By setting `brokerMode` to `true`. MQTTProxy can both send msg to backend and subscribers. Users can also send msg to clients by using HTTP endpoint. + +# Example +Save following yaml to file `mqttproxy.yaml` and then run +```bash +egctl create -f mqttproxy.yaml +``` +```yaml +kind: MQTTProxy +name: mqttproxy +port: 1883 # tcp port for mqtt clients to connect +useTLS: true +certificate: +- name: cert1 + cert: balabala + key: keyForbalabala +- name: cert2 + cert: foo + key: bar +rules: +- when: + packetType: Connect + pipeline: pipeline-mqtt-auth +- when: + packetType: Publish + pipeline: pipeline-mqtt-publish +# by default, brokerMode is disabled. +brokerMode: true + +--- + +name: pipeline-mqtt-auth +kind: Pipeline +protocol: MQTT +flow: +- filter: auth +filters: +- name: auth + kind: MQTTClientAuth + salt: salt + auth: + # username and password are both test + - username: test + saltedSha256Pass: 1bc1a361f17092bc7af4b2f82bf9194ea9ee2ca49eb2e53e39f555bc1eeaed74 + +--- + +name: pipeline-mqtt-publish +kind: Pipeline +protocol: MQTT +flow: +- filter: publish-kafka-backend +filters: +- name: publish-kafka-backend + kind: KafkaMQTT + backend: ["127.0.0.1:9092"] + topic: + default: kafka-topic +``` +In this example, we use pipeline to process MQTT Connect packet (check username and password) and Publish packet (send to Kafka backend). + +Now, we support following filters for MQTTProxy: +- `TopicMapper`: map MQTT Publish packet multi-level topic into single topic and key-value headers. +- `MQTTClientAuth`: provide username and password checking for MQTT Connect packet. +- `KafkaMQTT`: send MQTT Publish message to Kafka backend. By default, `KafkaMQTT` filter will add `clientID`, `username`, `mqttTopic` to Kafka message headers. + +# Topic Mapping +In MQTT, there are multi-levels in a topic. Topic mapping is used to map MQTT topic to a single topic with headers. For example: +``` +MQTT multi-level topics: +- beijing/car/123/log +- shanghai/tv/234/status +- nanjing/phone/456/error + +with corresponding pattern: +- loc/device/ID/event + +with Topic mapper, may produce Kafka topic: +- topic: iot_device, headers: {loc: beijing, device: car, ID: 123, event: log} +- topic: iot_device, headers: {loc: shanghai, device: tv, ID: 234, event: status} +- topic: iot_device, headers: {loc: nanjing, device: phone, ID: 456, event: error} +``` + +Topic mapping can make processing MQTT messages easier. In Easegress, we use filter `TopicMapper` to do topic mapping. + +Here's a simple example: +```yaml +name: pipeline-mqtt-publish +kind: Pipeline +protocol: MQTT +flow: +- filter: topic-mapper +- filter: publish-kafka-backend +filters: +- name: topic-mapper + kind: TopicMapper + setKV: # setKV set topic and header map into MQTT Context + topic: kafka-topic + headers: kafka-headers + # matchIndex and route will decide which policy we use to do the mapping for MQTT topic + matchIndex: 0 + route: + - name: gateway + matchExpr: "gate*" + - name: direct + matchExpr: "dir*" + # policies define how to create topic and header map by using MQTT publish topic. + policies: + - name: direct + topicIndex: 1 + route: + - topic: iot_phone + exprs: ["iphone", "xiaomi", "oppo", "pixel"] + - topic: iot_other + exprs: [".*"] + headers: + 0: direct + 1: device + 2: status + - name: gateway + topicIndex: 3 + route: + - topic: iot_phone + exprs: ["iphone", "xiaomi", "oppo", "pixel"] + - topic: iot_other + exprs: [".*"] + headers: + 0: gateway + 1: gatewayID + 2: device + 3: status +- name: publish-kafka-backend + kind: KafkaMQTT + backend: ["my-cluster-kafka-bootstrap.kafka:9092"] + topic: + default: kafka-topic + mqtt: + # since we don't use original topic name in MQTT Publish packet. + # we need keys to get topic and header map from MQTT Context. + topicKey: kafka-topic + headerKey: kafka-headers +``` + +## Match different topic mapping policy +Consider there may be multiple schemas for your MQTT topic, so we first provide a router to route your MQTT topic to different mapping policies and then do the map in that policy. + +For example, +``` +schema1: gateway/gatewayID/device/status +schema2: direct/device/status + +... + # matchIndex is the MQTT topic level used to match the route policy + matchIndex: 0 + route: + - name: gateway + matchExpr: "gate*" + - name: direct + matchExpr: "dir*" +... +``` +means that we use MQTT topic level 0 to match `matchExpr` to find a corresponding policy. In this case, `gateway/gate123/iphone/log` will match policy `gateway`, `direct/iphone/log` will match policy `direct`. + + +## Detail of single policy +``` + policies: + - name: direct + topicIndex: 1 + route: + - topic: iot_phone + exprs: ["iphone", "xiaomi", "oppo", "pixel"] + - topic: iot_other + exprs: [".*"] + headers: + 0: direct + 1: device + 2: status +``` +`topicIndex` is the MQTT topic level used to produce Kafka topic (Regular expressions supported), in this case, `direct/iphone/...` will produce Kafka topic `iot_phone`, but `direct/car/...` will produce Kafka topic `iot_other`. `headers` used to produce Kafka headers. + +More example about topic mapper: +``` +use yaml above: +MQTT topic: +pattern1: gateway/gatewayID/device/status -> match policy gateway +pattern2: direct/device/status -> match policy direct + +example1: "gateway/gate123/iphone/log" +topic and header map: + topic: iot_phone + headers: + gateway: gateway + gatewayID: gate123 + device: iphone + status: log + +example2: "direct/xiaomi/status" +topic and header map: + topic: iot_phone + headers: + direct: direct + device: xiaomi + status: status + +example3: "direct/tv/log" +topic and header map: + topic: iot_other + headers: + direct: direct + device: tv + status: log +``` +Empty `topicMapper` means there is no map between the MQTT topic and Kafka topic. + +> Note: For MQTT topic `"gateway/gate123/iphone/log"`, index 0 is `"gateway"`. For `"/gateway/gate123/iphone/log"` index 0 is still `"gateway"` not `""`. So, index 0 is the first non-empty level of multi-level MQTT topic. + +# HTTP endpoint +We support the backend to send messages back to MQTT clients through the HTTP endpoint. + +API for http endpoint: +- Host: Easegress IP, for example `http://127.0.0.1` +- Port: Easegress API address, by default, `:2381` +- Path: `apis/v1/mqttproxy/{name}/topics/publish`, where name is the name of MQTT proxy +- Method: POST +- Body: +```json +{ + "topic": "yourTopicName", + "qos": 1, + "payload": "dataPayload", + "base64": false +} +``` +> Note: Currently, the QoS only support `0` and `1` + +To send binary data, you can encode your binary data base64 and send `base64` flag to `true`. Your client will receive the original binary data, we will do the decode. +- Status code: + - 200: Success + - 400: StatusBadRequest, may wrong http method, or wrong data (qos send to illegal number) etc. + +The HTTP endpoint schema also works for multi-node deployment. Say you have 3 Easegress instances called `eg-0`, `eg-1`, `eg-2`, and your MQTT client connects to `eg-0`, if you send messages to `eg-1`, your client will receive the message too. + +We also support wildcard subscriptions. +For example, +``` +POST http://127.0.0.1:2381/apis/v2/mqttproxy/mqttproxy/topics/publish +{ + "topic": "Beijing/Phone/Update", + "qos": 1, // currently only support 0 and 1 + "payload": "time to update", + "base64": false +} + +the clients subscribe following topics will receive the message: +"Beijing/Phone/Update" +"Beijing/+/Update" +"Beijing/Phone/+" +"+/Phone/Update" +"Beijing/+/+" +"Beijing/#" +"+/+/+" +``` + +# References +1. https://github.com/eclipse/paho.mqtt.golang +2. http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html diff --git a/docs/03.Advanced-Cookbook/3.02.Flash-Sale.md b/docs/03.Advanced-Cookbook/3.02.Flash-Sale.md index cd3367e997..a12f159a5e 100644 --- a/docs/03.Advanced-Cookbook/3.02.Flash-Sale.md +++ b/docs/03.Advanced-Cookbook/3.02.Flash-Sale.md @@ -1,3 +1,493 @@ -TODO: +# Handle Flash Sale With Easegress and WebAssembly -from flashsale.md +- [Handle Flash Sale With Easegress and WebAssembly](#handle-flash-sale-with-easegress-and-webassembly) + - [1. Getting Started](#1-getting-started) + - [1.1 Setting Up The Flash Sale Project](#11-setting-up-the-flash-sale-project) + - [1.2 Creating HTTP Server And Pipeline In Easegress](#12-creating-http-server-and-pipeline-in-easegress) + - [1.3 Verify What We Have Done](#13-verify-what-we-have-done) + - [2. Block All Requests Before Flash Sale Start](#2-block-all-requests-before-flash-sale-start) + - [3. Randomly Block Requests](#3-randomly-block-requests) + - [4. Lucky Once, Lucky Always](#4-lucky-once-lucky-always) + - [5. Limit The Number Of Permitted Users](#5-limit-the-number-of-permitted-users) + - [6. Reuse The Program](#6-reuse-the-program) + - [6.1 Parameters](#61-parameters) + - [6.2 Manage Shared Data](#62-manage-shared-data) + - [7. Summary](#7-summary) + +A flash sale is a discount or promotion offered by an eCommerce store for a short period. The quantity is limited, which often means the discounts are higher or more significant than run-of-the-mill promotions. + +However, significant discounts, limited quantity, and a short period leading to a significant high traffic spike, which often results in slow service, denial of service, or even downtime. + +This document illustrates how to leverage the [WasmHost Filter](https://github.com/megaease/easegress/blob/main/doc/reference/wasmhost.md) to protect the backend service in a flash sale. The WebAssembly code is written in [AssemblyScript](https://www.assemblyscript.org/) by using the [Easegress AssemblyScript SDK](https://github.com/megaease/easegress-assemblyscript-sdk). + +Before we start, we need to introduce why we use a service gateway with WebAssembly. Firstly, Easegress as a service gateway is more responsible for the control logic. Secondly, the business logic like the flash sale would be a more customized thing and could be changed frequently. Using Javascript or other high-level languages to write business logic could bring good productivity and lower technical barriers. With WebAssembly technology, the high-level languages code can be compiled to WASM and loaded dynamically at runtime. Furthermore, the WebAssembly code has good enough performance and security. So this combination can provide a perfect solution in terms of security, high performance, and customization extensions. + +## 1. Getting Started + +Please ensure a recent version of [Git](https://git-scm.com/), [Golang](https://golang.org), [Node.js](https://nodejs.org/) and its package manager [npm](https://www.npmjs.com/) are installed before continue. Basic knowledge about writing and working with TypeScript modules, which is very similar to AssemblyScript, is a plus. + +**Note**: The `WasmHost` filter is disabled by default. To enable it, you need to build Easegress with the below command: + +```bash +$ make build_server GOTAGS=wasmhost +``` + +### 1.1 Setting Up The Flash Sale Project + +1 ) Clone git repository `easegress-assemblyscript-sdk` to somewhere on disk + +```bash +$ git clone https://github.com/megaease/easegress-assemblyscript-sdk.git +``` + +2 ) Switch to a new directory and initialize a new node module: + +```bash +npm init +``` + +3 ) Install the AssemblyScript compiler using npm, assume that the compiler is not required in production, and make it a development dependency: + +```bash +npm install --save-dev assemblyscript +``` + +4 ) Once installed, the compiler provides a handy scaffolding utility to quickly set up a new AssemblyScript project, for example, in the directory of the just initialized node module: + +```bash +npx asinit . +``` + +5 ) Add `--use abort=` to the `asc` in `package.json`, for example: + +```json +"asbuild:untouched": "asc assembly/index.ts --target debug --use abort=", +"asbuild:optimized": "asc assembly/index.ts --target release --use abort=", +``` + +6 ) Replace the content of `assembly/index.ts` with the code below, note to replace `{EASEGRESS_SDK_PATH}` with the path in step 1). The code is just a skeleton and does "nothing" at present, it will be enhanced later: + +```typescript +// this line exports everything required by Easegress, +export * from '{EASEGRESS_SDK_PATH}/easegress/proxy' + +// import everything you need from the SDK, +import { Program, registerProgramFactory } from '{EASEGRESS_SDK_PATH}/easegress' + +// define the program, 'FlashSale' is the name +class FlashSale extends Program { + // constructor is the initializer of the program, will be called once at the startup + constructor(params: Map) { + super(params) + } + + // run will be called for every request + run(): i32 { + return 0 + } +} + +// register a factory method of the FlashSale program +registerProgramFactory((params: Map) => { + return new FlashSale(params) +}) +``` + +7 ) Build with the below command, if everything is right, `untouched.wasm` (the debug version) and `optimized.wasm` (the release version) will be generated at the `build` folder. + +```bash +$ npm run asbuild +``` + +### 1.2 Creating HTTP Server And Pipeline In Easegress + +Create an HTTPServer in Easegress to listen on port 10080 to handle the HTTP traffic: + +```bash +$ echo ' +kind: HTTPServer +name: http-server +port: 10080 +keepAlive: true +https: false +rules: +- paths: + - pathPrefix: /flashsale + backend: flash-sale-pipeline' | egctl create -f - +``` + +Create pipeline `flash-sale-pipeline` which includes a `WasmHost` filter: + +```bash +$ echo ' +name: flash-sale-pipeline +kind: Pipeline +flow: +- filter: wasm +- filter: mock + +filters: +- name: wasm + kind: WasmHost + maxConcurrency: 2 + code: /home/megaease/example/build/optimized.wasm + timeout: 100ms +- name: mock + kind: Mock + rules: + - body: "You can buy the laptop for $1 now.\n" + code: 200' | egctl create -f - +``` + +Note to replace `/home/megaease/example/build/optimized.wasm` with the path of the file generated in step 7) of section 1.1. + +In the above pipeline configuration, a `Mock` filter is used as the backend service. In practice, you will need a `Proxy` filter to forward requests to the real backend. + +### 1.3 Verify What We Have Done + +Execute the below command from a new console, you should get a similar result if everything is right: + +```bash +$ curl http://127.0.0.1:10080/flashsale +You can buy the laptop for $1 now. +``` + +## 2. Block All Requests Before Flash Sale Start + +All flash sale promotions have a start time, requests before the time should be blocked. Suppose the start time is UTC `2021-08-08 00:00:00`, this can be accomplished with the below code: + +```typescript +export * from '{EASEGRESS_SDK_PATH}/easegress/proxy' + +import { Program, response, parseDate, getUnixTimeInMs, registerProgramFactory } from '{EASEGRESS_SDK_PATH}/easegress' + +class FlashSale extends Program { + // startTime is the start time of the flash sale, unix timestamp in millisecond + startTime: i64 + + constructor(params: Map) { + super(params) + this.startTime = parseDate("2021-08-08T00:00:00+00:00").getTime() + } + + run(): i32 { + // if flash sale not start yet + if (getUnixTimeInMs() < this.startTime) { + // we just set response body to 'not start yet' here, in practice, + // we will use 'response.setStatusCode(302)' to redirect user to + // a static page. + response.setBody(String.UTF8.encode("not start yet.\n")) + return 1 + } + + return 0 + } +} + +registerProgramFactory((params: Map) => { + return new FlashSale(params) +}) +``` + +Build and inform Easegress to reload with: + +```bash +$ npm run asbuild +$ egctl wasm reload-code +``` + +`curl` the flash sale URL, we will get `not start yet.` before the start time of the flash sale. + +```bash +$ curl http://127.0.0.1:10080/flashsale +not start yet. +``` + +## 3. Randomly Block Requests + +After the start of the flash sale, Easegress should block requests randomly, this greatly reduces the total number of requests sent to the backend service, which protects the service from the strike of the traffic spike. The randomness brings another benefit also: geography differences result in latency differences, users with a lower latency are more likely to be the early users. The randomness removes the edge of these users and makes the flash sale fairer. + +```typescript +export * from '{EASEGRESS_SDK_PATH}/easegress/proxy' + +import { Program, response, parseDate, getUnixTimeInMs, rand, registerProgramFactory } from '{EASEGRESS_SDK_PATH}/easegress' + +class FlashSale extends Program { + startTime: i64 + + // blockRatio is the ratio of requests being blocked to protect backend service + // for example: 0.4 means we blocks 40% of the requests randomly. + blockRatio: f64 + + constructor(params: Map) { + super(params) + this.startTime = parseDate("2021-08-08T00:00:00+00:00").getTime() + this.blockRatio = 0.4 + } + + run(): i32 { + if (getUnixTimeInMs() < this.startTime) { + response.setBody(String.UTF8.encode("not start yet.\n")) + return 1 + } + + if (rand() > this.blockRatio) { + // the lucky guy + return 0 + } + + // block this request, set response body to `sold out` + response.setBody(String.UTF8.encode("sold out.\n")) + return 2 + } +} + +registerProgramFactory((params: Map) => { + return new FlashSale(params) +}) +``` + +Build and verify with (suppose the flash sale was already started): + +```bash +$ npm run asbuild +$ egctl wasm apply-data --reload-code +$ curl http://127.0.0.1:10080/flashsale +sold out. +$ curl http://127.0.0.1:10080/flashsale +You can buy the laptop for $1 now. +$ curl http://127.0.0.1:10080/flashsale +sold out. +``` + +We will get a `sold out` message at the possibility of 40%. Note the `blockRatio` is `0.4` in this example, while in practice, `0.999`, `0.9999` will be much more make sense. + +## 4. Lucky Once, Lucky Always + +From the view of business, after we permit a lucky user to go forward, we should always permit this user to go forward; but from the logic of the code in the last step, the request may be blocked if the user accesses the URL again. + +Fortunately, all users need to sign in before joining the flash sale, that's the requests will contain an identifier of the user, we can use this identifier to record the lucky users. + +As an example, we suppose the value of the `Authorization` header is the desired identifier (the identifier could be a JWT token, and the [Validator filter](./security.md#security-verify-credential) can be used to validate the token, but this is out of the scope of this document). + +However, due to the `maxConcurrency` option in the filter configuration, using a `Set` the store all permitted users won't work. + +`maxConcurrency` is the number of WebAssembly VMs of the `WasmHost` filter, and because WebAssembly is designed to be safe, two VMs can not share data even if they are executing the same copy of code. That is, after VM1 permits a user, if the next request of the user is processed by VM2, it could be blocked. This could also happen when Easegress is deployed as a cluster. + +To overcome this issue, Easegress provide APIs to access shared data: + +```typescript +export * from '{EASEGRESS_SDK_PATH}/easegress/proxy' + +import { Program, request, parseDate, response, cluster, getUnixTimeInMs, rand, registerProgramFactory } from '{EASEGRESS_SDK_PATH}/easegress' + +class FlashSale extends Program { + startTime: i64 + blockRatio: f64 + + constructor(params: Map) { + super(params) + this.startTime = parseDate("2021-08-08T00:00:00+00:00").getTime() + this.blockRatio = 0.4 + } + + run(): i32 { + if (getUnixTimeInMs() < this.startTime) { + response.setBody(String.UTF8.encode("not start yet.\n")) + return 1 + } + + // check if the user was already permitted + let id = request.getHeader("Authorization") + if (cluster.getString("id/" + id) == "true") { + return 0 + } + + if (rand() > this.blockRatio) { + // add the lucky guy to permitted users + cluster.putString("id/" + id, "true") + return 0 + } + + response.setBody(String.UTF8.encode("sold out.\n")) + return 2 + } +} + +registerProgramFactory((params: Map) => { + return new FlashSale(params) +}) +``` + +Build and verify with: + +```bash +$ npm run asbuild +$ egctl wasm apply-data --reload-code +$ curl http://127.0.0.1:10080/flashsale -HAuthorization:user1 +sold out. +$ curl http://127.0.0.1:10080/flashsale -HAuthorization:user1 +You can buy the laptop for $1 now. +$ curl http://127.0.0.1:10080/flashsale -HAuthorization:user1 +You can buy the laptop for $1 now. +``` + +Repeat the `curl` command, we will find out that the user will never be blocked again after he/she was permitted for the first time. + +## 5. Limit The Number Of Permitted Users + +As the quantity is often limited in a flash sale, we can block users after we have permitted a number of users. For example, if the quantity is 10, permit 100 users is enough in most cases: + +```typescript +export * from '{EASEGRESS_SDK_PATH}/easegress/proxy' + +import { Program, request, parseDate, response, cluster, getUnixTimeInMs, rand, registerProgramFactory } from '{EASEGRESS_SDK_PATH}/easegress' + +class FlashSale extends Program { + startTime: i64 + blockRatio: f64 + + // maxPermission is the upper limits of permitted users + maxPermission: i32 + + constructor(params: Map) { + super(params) + this.startTime = parseDate("2021-08-08T00:00:00+00:00").getTime() + this.blockRatio = 0.4 + this.maxPermission = 3 + } + + run(): i32 { + if (getUnixTimeInMs() < this.startTime) { + response.setBody(String.UTF8.encode("not start yet.\n")) + return 1 + } + + let id = request.getHeader("Authorization") + if (cluster.getString("id/" + id) == "true") { + return 0 + } + + // check the count of identifiers to see if we have reached the upper limit + if (cluster.countKey("id/") < this.maxPermission) { + if (rand() > this.blockRatio) { + cluster.putString("id/" + id, "true") + return 0 + } + } + + response.setBody(String.UTF8.encode("sold out.\n")) + return 2 + } +} + +registerProgramFactory((params: Map) => { + return new FlashSale(params) +}) +``` + +Build and verify with: + +```bash +$ npm run asbuild +$ egctl wasm apply-data --reload-code +$ curl http://127.0.0.1:10080/flashsale -HAuthorization:user1 +You can buy the laptop for $1 now. +$ curl http://127.0.0.1:10080/flashsale -HAuthorization:user2 +sold out. +$ curl http://127.0.0.1:10080/flashsale -HAuthorization:user2 +You can buy the laptop for $1 now. +$ curl http://127.0.0.1:10080/flashsale -HAuthorization:user3 +You can buy the laptop for $1 now. +$ curl http://127.0.0.1:10080/flashsale -HAuthorization:user4 +sold out. +$ curl http://127.0.0.1:10080/flashsale -HAuthorization:user4 +sold out. +``` + +After 3 users were permitted, the 4th user is blocked forever. + +## 6. Reuse The Program + +### 6.1 Parameters + +We have hard-coded `startTime`, `blockRatio`, and `maxPermission` in the above examples, which means if we have another flash sale, we need to modify the code. This is not a good practice. + +A better approach is putting these parameters into the configuration: + +```yaml +filters: + - name: wasm + kind: WasmHost + parameters: # + + startTime: "2021-08-08T00:00:00+00:00" # + + blockRatio: "0.4" # + + maxPermission: "3" # + +``` + +And then revise the `constructor` of the program to read in these parameters: + +```typescript + constructor(params: Map) { + super(params) + + let key = "startTime" + if (params.has(key)) { + let val = params.get(key) + this.startTime = parseDate(val).getTime() + } + + key = "blockRatio" + if (params.has(key)) { + let val = params.get(key) + this.blockRatio = parseFloat(val) + } + + key = "maxPermission" + if (params.has(key)) { + let val = params.get(key) + this.maxPermission = i32(parseInt(val)) + } + } +``` + +### 6.2 Manage Shared Data + +As we can see in [Lucky Once, Lucky Always](#4-lucky-once-lucky-always), shared data is useful, but when reusing the code and configuration for a new flash sale event, the legacy data could cause problems. Easegress provides commands to manage these data. + +We can view current data with (where `flash-sale-pipeline` is the pipeline name and `wasm` is the filter name): + +```bash +$ egctl wasm list-data flash-sale-pipeline wasm +id/user1: "true" +id/user2: "true" +id/user3: "true" +``` + +Update the data with: + +```bash +$ echo ' +id/user4: "true" +id/user5: "true"' | egctl wasm apply-data flash-sale-pipeline wasm +$ egctl wasm list-data flash-sale-pipeline wasm +id/user1: "true" +id/user2: "true" +id/user3: "true" +id/user4: "true" +id/user5: "true" +``` + +And delete all data with: + +```bash +$ egctl wasm delete-data flash-sale-pipeline wasm +$ egctl wasm list-data flash-sale-pipeline wasm +{} +``` + +Well, the above is all technical details. You can freely use the code to customize your business logic. However, it should be aware that **the above is just a demo, and the practical solution is more complicated because it also needs to filter the crawlers and hackers**. If you need a more professional solution, welcome to contact us. + +## 7. Summary + +Using WebAssembly's security, high performance, and real-time dynamic loading capabilities, we can not only do such a high concurrency business on the gateway but also achieve some more complex business logic support. Because WebAssembly can reuse a variety of high-level languages (such as Javascript, C/C++, Rust, Python, C#, etc.). Easegress has more capabilities to play in a high-performance traffic orchestration with distributed architecture. Both of there bring much imagination to solve the problem with efficient operation and maintenance. diff --git a/docs/03.Advanced-Cookbook/3.03.Multiple-API-Orchestration.md b/docs/03.Advanced-Cookbook/3.03.Multiple-API-Orchestration.md new file mode 100644 index 0000000000..8b67bb845e --- /dev/null +++ b/docs/03.Advanced-Cookbook/3.03.Multiple-API-Orchestration.md @@ -0,0 +1,417 @@ +# Build A Telegram Translation Bot With Easegress + +Easegress is the next-generation traffic-based gateway product developed by MegaEase. It is completely architected on top of cloud-native technology, avoiding the shortcomings of traditional reverse proxy in terms of high availability, traffic orchestration, monitoring, service discovery, etc. + +We released Easegress v2.0 recently, with another significant enhancement to traffic orchestration, allowing users to implement a super API by orchestrating multiple APIs without writing any code. This article will demonstrate this feature by building a Telegram translation bot. This bot can automatically translate incoming messages into Chinese, Japanese, and English, and, in addition to text messages, it also supports translating voice and photo messages. + +## 1. Prerequisites + +Since the bot needs to receive Telegram message notifications and call third-party APIs, we must prepare the following in advance: + +* Install the latest version of Easegress according to [this document](https://github.com/megaease/easegress#setting-up-easegress) and make sure that external applications can access the Easegress instance on at least one of ports 80, 88, 443, or 8443. +* Create a Telegram bot by following [this document](https://core.telegram.org/bots#3-how-do-i-create-a-bot), set its name (EaseTranslateBot is used in this article), write down its token, and [set up a Webhook](https://core.telegram.org/bots/api#setwebhook) that points to the Easegress instance installed in the previous step. Our bot will receive notifications of new messages through this Webhook. +* AWS Access Key ID and Access Key Secret, and ensure that you can use the AWS translation API with this Access Key. +* Google Cloud's Token and ensure that you can use Google Cloud's Speech Recognize API and OCR (Image Annotation) API with this Token. + +It is fine for you to use other vendors' translation, speech recognition, or OCR APIs, but this will require you to adapt the examples in the later sections accordingly. + +## 2. How It Works + +The diagram below shows the workflow of this bot: + +![diagram](../imgs/translation-bot-workflow.png) + +Upon receiving a notification of a new message from the Telegram server via webhook, the bot first checks the message type and does the following accordingly: + +* **Text Message**: Extract message text directly; +* **Voice Message**: In this case, the message body only contains the ID of the voice file, so we need to call Telegram's API to convert the ID to a file address, then download the file and send its contents to Google's voice recognition service to convert it to text; +* **Photo Message**: Basically, this is the same as the voice message, but the file content is sent to Google's Image Annotation service. + +After the above processing, all three types of messages are turned into text, and then AWS translation service can be called to translate them into target languages, the target languages used in this example are Chinese, Japanese, and English. + +## 3. Pipeline + +First, let's check the overall flow that Pipeline orchestrates: + +```yaml +flow: +# we put the final response builder at the top because Telegram requires +# us to return a response for every Request, but we only process some of +# the requests. If we put it at the end, the requests we don't process +# will end the process early and no Response will be returned. +- filter: buildFinalResponse + +# detect message type and jump accordingly +- filter: detectMessageType + jumpIf: + result0: processText # text + result1: processVoice # voice + result2: processPhoto # photo + "": END # ignore, end the processing + +# text message +- filter: requestBuilderExtractText + alias: processText # alias of the filter + namespace: extract # namespace the filter belongs to + jumpIf: # conditional jump, begin translation + "": translate # if everything is fine, or end the + # processing otherwise. + +# voice message +- filter: requestBuilderGetVoiceFile # Constructing a request to convert a + alias: processVoice # voice file ID to a path. + namespace: extract +- filter: proxyTelegram # send the request to retrieve file path. + namespace: extract +- filter: requestBuilderDownloadFile # Constructing a request to download + namespace: extract # the voice file +- filter: proxyTelegram # send the request to retrieve file content + namespace: extract +- filter: requestBuilderSpeechRecognize # Constructing a request to call the + namespace: extract # speech recognition API +- filter: proxySpeechRecognize # send the request to retrieve the + namespace: extract # recognition result +- filter: requestBuilderSpeechText # Save recognition result + namespace: extract + jumpIf: # conditional jump, begin translation + "": translate # if everything is fine, or end the + # processing otherwise. + +# photo message (the process is basically the same as for voice message) +- filter: requestBuilderGetPhotoFile + alias: processPhoto + namespace: extract +- filter: proxyTelegram + namespace: extract +- filter: requestBuilderDownloadFile + namespace: extract +- filter: proxyTelegram + namespace: extract +- filter: requestBuilderImageAnnotate + namespace: extract +- filter: proxyImageAnnotate + namespace: extract +- filter: requestBuilderPhotoText # no need to jump + namespace: extract + +# translate to Chinese +- filter: requestBuilderTranslate # Constructing the request to call the + alias: translate # translation API + namespace: zh +- filter: signAWSRequest # Signing as required by AWS + namespace: zh +- filter: proxyTranslate # Send request to retrieve translation + namespace: zh # result + +# translate to English (same process as Chinese translation) +- filter: requestBuilderTranslate + namespace: en +- filter: signAWSRequest + namespace: en +- filter: proxyTranslate + namespace: en + +# translate to Japanese (same process as Chinese translation) +- filter: requestBuilderTranslate + namespace: ja +- filter: signAWSRequest + namespace: ja +- filter: proxyTranslate + namespace: ja + +# reply, send the translation to Telegram +- filter: requestBuilderReply # constructing a request to send the reply + namespace: tg +- filter: proxyTelegram # send the request to Telegram + namespace: tg +``` + +As we have already explained the basic idea behind the bot, it is easy to see the whole process from the flow above. However, because the final response needs to combine the execution results of multiple APIs, we need to use multiple namespaces to store the parameters and execution results of these APIs, i.e., the requests sent and the responses they return. + +And, to achieve a better effect, we also defined some data on the pipeline: + +```yaml +data: + zh: + fallback: "(抱歉,我不懂这种语言。)" + text: "中文🇨🇳" + ja: + fallback: "(申し訳ないのですが、この言葉は本当に初めて見ました。)" + text: "やまと🇯🇵" + en: + fallback: "(I'm sorry, but I really don't know this language.)" + text: "English🇬🇧" +``` + +where `zh`, `ja` and `en` are the language codes for Chinese, Japanese and English, `text` is the language name and the corresponding flag, and `fallback` is the replacement text in case of translation failure, as shown below: + +![pipe line data](../imgs/translation-bot-data.png) + +## 4. Filter + +In Easegress, Filter is the component that handles the traffic, specifically in this example, Pipeline is responsible for orchestrating the flow while detecting message types and calling third-party APIs are done by Filter. + +### 4.1 Backend Proxies + +All external API requests are sent through the Proxy Filter, this example uses four external services, so there are four Proxy filters, as their configuration is very simple, I will not do more to introduce them. + +```yaml +# Google Image Annotate +name: proxyImageAnnotate +kind: Proxy +pools: +- servers: + - url: https://vision.googleapis.com + +# Google Speech Recognize +name: proxySpeechRecognize +kind: Proxy +pools: +- servers: + - url: https://speech.googleapis.com + +# AWS Translate +name: proxyTranslate +kind: Proxy +pools: +- servers: + - url: https://translate.us-east-2.amazonaws.com + +# Telegram +name: proxyTelegram +kind: Proxy +pools: +- servers: + - url: https://api.telegram.org +``` + +### 4.2 Detect Message Type + +This is done by a ResultBuilder Filter, configured as follows: + +```yaml +kind: ResultBuilder +name: detectMessageType +template: | + {{- $msg := or .requests.DEFAULT.JSONBody.message .requests.DEFAULT.JSONBody.channel_post -}} + {{- if $msg.text}}result0{{else if $msg.voice}}result1{{else if $msg.photo}}result2{{end -}} +``` + +Its template field is a template written according to the requirements of the [Go text/template package](https://pkg.go.dev/text/template), which generates a string at runtime that the ResultBuilder returns to Pipeline as its execution result, and Pipeline can jump based on this execution result. In other words, ResultBuilder and Pipeline work together to implement switch-case functionality similar to that of programming languages. + +A message in Telegram may come from a user group or from a channel, the field representing the message body is different, so the template determines this first, but in both cases, the format of the message body is the same. + +`DEFAULT` is the namespace to which the request belongs, where `.requests.DEFAULT` is the HTTP request sent by Telegram via webhook with the message. By checking the validity of the `text`, `voice`, and `photo` fields in the message body, we can know the message type. + +Currently, the result of ResultBuilder can only be `result0` - `result9`, so we use `result0` for text messages, `result1` for voice messages, and `result2` for photo messages. Later we will enhance this filter to make the result more readable. + +### 4.3 Reading The File Content + +Both voice and photo messages need to first convert the file ID in the message to a file path and then read the file to get its content, which is done using the following filters: + +```yaml +# Convert voice file ID to path +kind: RequestBuilder +name: requestBuilderGetVoiceFile +template: | + {{$msg := or .requests.DEFAULT.JSONBody.message .requests.DEFAULT.JSONBody.channel_post}} + method: GET + url: https://api.telegram.org/bot{YOUR BOT TOKEN}/getFile?file_id={{$msg.voice.file_id}} + +# Convert photo file ID to path +kind: RequestBuilder +name: requestBuilderGetPhotoFile +template: | + {{$msg := or .requests.DEFAULT.JSONBody.message .requests.DEFAULT.JSONBody.channel_post}} + method: GET + url: https://api.telegram.org/bot{YOUR BOT TOKEN}/getFile?file_id={{(last $msg.photo).file_id}} + +# Download(read) file +kind: RequestBuilder +name: requestBuilderDownloadFile +template: | + method: GET + url: https://api.telegram.org/file/bot{YOUR BOT TOKEN}/{{.responses.extract.JSONBody.result.file_path}} +``` + +Note that the step of converting file ID to path is a bit more complicated for photos than for voice. This is because, for the same original photo, Telegram may generate multiple thumbnails of different sizes and send all the thumbnails together with the original photo, while the last one is the original. + +### 4.4 Speech Recognize and OCR + +These two filters are slightly complex, but both are simply creating the corresponding HTTP requests as requested by the third-party service. + +```yaml +# Speech Recognize +kind: RequestBuilder +name: requestBuilderSpeechRecognize +template: | + url: https://speech.googleapis.com/v1/speech:recognize?key={YOUR GOOGLE API KEY}} + method: POST + body: | + { + "config": { + "languageCode": "zh", + "alternativeLanguageCodes": ["en-US", "ja-JP"], + "enableAutomaticPunctuation": true, + "model": "default", + "encoding": "OGG_OPUS", + "sampleRateHertz": 48000 + }, + "audio": { + "content": "{{.responses.extract.Body | b64enc}}" + } + } + +# OCR +kind: RequestBuilder +name: requestBuilderImageAnnotate +template: | + url: https://vision.googleapis.com/v1/images:annotate?key={YOUR GOOGLE API KEY}} + method: POST + body: | + { + "requests": [{ + "features": [{ + "type": "TEXT_DETECTION", + "maxResults": 50, + "model": "builtin/latest" + }], + "image": { + "content": "{{.responses.extract.Body | b64enc}}" + } + }] + } +``` + +### 4.5 Text Extraction + +For each of the three different message types, we use a filter for text extraction. + +```yaml +# Extract text from text message +kind: RequestBuilder +name: requestBuilderExtractText +template: | + {{- $msg := or .requests.DEFAULT.JSONBody.message .requests.DEFAULT.JSONBody.channel_post -}} + body: | + { + "exclude": true, + "text": "{{$msg.text | jsonEscape}}" + } + +# Extract Text From Voice(Speech) Message +kind: RequestBuilder +name: requestBuilderSpeechText +template: | + {{$result := index .responses.extract.JSONBody.results 0}} + {{$result = index $result.alternatives 0}} + body: | + {"text": "{{$result.transcript | jsonEscape}}"} + +# Extract Text From Photo Message +kind: RequestBuilder +name: requestBuilderPhotoText +template: | + {{$result := index .responses.extract.JSONBody.responses 0}} + body: | + {"text": "{{replace "\n" " " $result.fullTextAnnotation.text | jsonEscape}}"} +``` + +You may have noticed that we use an `exclude` field in the text message, this is to exclude the original text in the translation result, while for the voice or photo message, the recognized text content may be inaccurate, so the recognized text should be kept for the user's reference. + +### 4.6 Translating + +Since AWS requires all requests to be signed, a RequestAdaptor is used to complete the signing after the request is created via RequestBuilder. + +```yaml +# Build AWS translate Request +kind: RequestBuilder +name: requestBuilderTranslate +template: | + method: POST + url: https://translate.us-east-2.amazonaws.com/TranslateText + headers: + "Content-Type": ["application/x-amz-json-1.1"] + "X-Amz-Target": ["AWSShineFrontendService_20170701.TranslateText"] + body: | + { + "SourceLanguageCode": "auto", + "TargetLanguageCode": "{{.namespace}}", + "Text": "{{.requests.extract.JSONBody.text | jsonEscape}}" + } + +# Sign the request +name: signAWSRequest +kind: RequestAdaptor +sign: + apiProvider: aws4 + accessKeyId: {YOUR AWS ACCESS KEY ID} + accessKeySecret: {YOUR AWS ACCESS KEY SECRET} + scopes: ["us-east-2", "translate"] +``` + +### 4.7 Construct Translation Results Into Reply Message + +This is the most complex filter in this article, but in general it is just organizing the information we got earlier, as Telegram requires. In particular, `$.data.PIPELINE` is referencing the data we have defined on the Pipeline. + +```yaml +kind: RequestBuilder +name: requestBuilderReply +template: | + {{$msg := or .requests.DEFAULT.JSONBody.message .requests.DEFAULT.JSONBody.channel_post}} + method: POST + url: https://api.telegram.org/bot{YOUR BOT TOKEN}/sendMessage + headers: + "Content-Type": ["application/json"] + body: | + { + "chat_id": {{$msg.chat.id}}, + "reply_to_message_id": {{$msg.message_id}}, + "text": "{{- range $ns, $resp := $.responses -}} + {{- if not (get $.data.PIPELINE $ns)}}{{continue}}{{end -}} + {{- if and $.requests.extract.JSONBody.exclude (eq $resp.JSONBody.SourceLanguageCode $resp.JSONBody.TargetLanguageCode)}}{{continue}}{{end -}} + {{- $lang := get $.data.PIPELINE $resp.JSONBody.TargetLanguageCode -}} + {{print $lang}}: {{printf "%s\n" $resp.JSONBody.TranslatedText | jsonEscape}} + {{- end}}" + } +``` + +### 4.8 Response + +Telegram requires us to return a response for each request. Since we don't need to reply to messages via this response, we can simply set the status code to 200. + +```yaml +kind: ResponseBuilder +name: buildFinalResponse +template: | + statusCode: 200 +``` + +## 5. Deploy + +Once we have prepared the configuration file (can be downloaded [here](https://github.com/megaease/easegress/tree/main/example/translation-bot)), we can deploy this Pipeline to Easegress (assuming the file name is `translate-pipeline.yaml`) via the following command. + +```bash +$ egctl create -f translate-pipeline.yaml +``` + +But we also need to create an HTTPServer and have it forward telegram's message notifications sent through the webhook to the above pipeline, note that the external access address of this pipeline must be the address of the Telegram webhook we created earlier. + +```bash +$ echo ' +kind: HTTPServer +name: httpserver +port: 8443 # telegram requires the port to be 80, 88, 443 or 8443 +https: true +autoCert: true # please set it to false if you are not using an AutoCertManager +keepAlive: true +keepAliveTimeout: 75s +maxConnection: 10240 +cacheSize: 0 +rules: +- paths: + - path: /translate + backend: translate-pipeline' | egctl create -f - +``` + +Now, we can test the bot in the chat. A demo video can be found at: https://www.youtube.com/watch?v=ne0OvV1FmvA. diff --git a/docs/03.Advanced-Cookbook/3.03.Telegram-Translation-Bot.md b/docs/03.Advanced-Cookbook/3.03.Telegram-Translation-Bot.md deleted file mode 100644 index 3a359124c4..0000000000 --- a/docs/03.Advanced-Cookbook/3.03.Telegram-Translation-Bot.md +++ /dev/null @@ -1,3 +0,0 @@ -TODO: - -from telegram.md \ No newline at end of file diff --git a/docs/03.Advanced-Cookbook/3.04.Canary-Release.md b/docs/03.Advanced-Cookbook/3.04.Canary-Release.md index 5767f68aee..4dd54a06b9 100644 --- a/docs/03.Advanced-Cookbook/3.04.Canary-Release.md +++ b/docs/03.Advanced-Cookbook/3.04.Canary-Release.md @@ -1,3 +1,263 @@ -TODO: +# Canary Release With Cloudflare -from canary.md +- [Canary Release With Cloudflare](#canary-release-with-cloudflare) + - [Background](#background) + - [Advantages](#advantages) + - [Key point](#key-point) + - [Why use Easegress and Cloudflare](#why-use-easegress-and-cloudflare) + - [based on geographic location](#based-on-geographic-location) + - [based on user devices](#based-on-user-devices) + - [based on user OS](#based-on-user-os) + +## Background + +A [canary release](https://martinfowler.com/bliki/CanaryRelease.html) is a software testing technique used to reduce the risk of introducing a new software version into production by gradually rolling out the change to a small subset of users, before rolling it out to the entire platform/infrastructure. + +### Advantages + +* **Service Evaluation**: You can evaluate multiple service versions side by side using canary release in real-world environments with actual users and use cases. +* **Zero downtime**: Canary release does not cause downtime +* **Simple Rollback Mechanism**: You can always easily roll back to the previous version. + +### Key point + +* **Traffic tagging**: To categorize traffic based on various business attributes such as **user devices**, **geographic locations**, or user attributes(e.g. age, gender, level, etc.), we can apply different labels or tags to the traffic. It's important to note that IP addresses should not be used as user tags as they can be inaccurate and inconsistent. +* **Traffic scheduling**: Once traffic is tagged, we can use traffic rules to schedule specific traffic to a designated Canary. For instance, traffic from an iPhone user in Beijing could be directed to a new version of a service. + +## Why use Easegress and Cloudflare + +* For traffic tagging, Using [Cloudflare](https://www.cloudflare.com/) we can easily label the traffic. + - **Geographic Location**: Cloudflare offers a feature called [Managed Transforms](https://developers.cloudflare.com/rules/transform/managed-transforms/reference/), which can be used to identify the geographic location of a user. + - **User Device**: Using [Workers](https://developers.cloudflare.com/workers/), we can extract information about the user's device, operating system, and web browser from the `User-Agent` field of the HTTP request. + - **User's Customized Label**: Using Cloudflare [Rules](https://developers.cloudflare.com/rules/transform/request-header-modification/), it is easy to extract the user's customized business label from HTTP request headers (such as: Cookies, etc.) + +- For traffic scheduling, Easegress can be used to route traffic to different canary releases. + +## based on geographic location + +![case 1](../imgs/canary-release-case-1.png) + +Suppose we run an e-Commerce service, and we recently upgraded our Order Service from version 1 to version 2. To ensure that our users have the best experience, we want to route the users who are accessing our service from Beijing to use the new Order service (version 2). + +Download the latest Service code from [canary-case-1](https://github.com/megaease/easegress-canary-example/tree/main/case1) + +**1. Traffic tagging** + +To determine the geographic location of a user, we can leverage the `CF-IPCity` field in Cloudflare's HTTP header. We need to first enable the "Add visitor location headers" option in the Cloudflare dashboard. (_Cloudflare Dashboard_ -> _Rules_ -> _Transform Rules_ -> _Managed Transforms_ -> [enabled “Add visitor location headers”](https://developers.cloudflare.com/rules/transform/managed-transforms/configure/) ) + +**2. Traffic scheduling rules:** + +Save the following YAML code to `order-service.yaml` and make sure to replace the `servers` configuration with your own. + +```yaml +name: order-pipeline +kind: Pipeline +flow: +- filter: order-service +filters: +- name: order-service + kind: Proxy + pools: + - servers: + - url: http://0.0.0.0:5002 # new version + filter: + headers: + cf-ipcity: # Cloudflare geographic location header + exact: Beijing # the traffic from Beijing should be directed to the new version + - servers: + - url: http://0.0.0.0:5001 # old version +``` + +Then create the order pipeline with the command: + +```bash +egctl create -f order-service.yaml +``` + +Save below YAML to `ecommerce-server.yaml`. + +```yaml +kind: HTTPServer +name: ecommerce-server +port: 8888 +https: false +keepAlive: true +keepAliveTimeout: 75s +maxConnection: 10240 +cacheSize: 0 +rules: + - paths: + - pathPrefix: /order + backend: order-pipeline + - pathPrefix: /notify + backend: notify-pipeline +``` + +Then create the HTTP server with command: + +```bash +egctl create -f ecommerce-server.yaml +``` + +Send a request to e-Commerce service: + +```bash +curl http://127.0.0.1:8888/order -H "cf-ipcity: Beijing" +{"order_id":"5245000","role_id":"44312","order_status":1,"order_version":"v2"} + +curl http://127.0.0.1:8888/order -H "cf-ipcity: Shanghai" +{"order_id":"5245000","role_id":"44312","order_status":1,"order_version":"v1"} +``` + +## based on user devices + +![case 2](../imgs/canary-release-case-2.png) + +In real-world scenarios, services are usually interconnected and depend on each other to provide a seamless experience for the customer. In our case, we recently upgraded our Notify Service from version 1 to version 2. To ensure that our users have a holistic experience, we want to route users who are accessing our service from a Mac device to use the new version of our Notify Service (version 2). + +Download the latest Service code from [canary-case-2](https://github.com/megaease/easegress-canary-example/tree/main/case2) + +**1. Traffic tagging** + +To determine the user's device and system, we can extract it from the `User-Agent` field in HTTP request by [Workers](https://developers.cloudflare.com/workers/). + +We offer an easy way to parse `User-Agent` in [easegress-rust-uaparser](https://github.com/megaease/easegress-rust-uaparser) library. + +```bash +git clone https://github.com/megaease/easegress-rust-uaparser.git +``` + +Go to the `easegress-rust-uaparser/cloudflare` directory and deploy the worker by using the `wrangler` command. + +```bash +npx wrangler publish +``` + +Go to the Cloudflare website configuration page and configure _Workers Routes_. + +**2. Traffic scheduling rules** + +Save the following YAML code to `order-service.yaml`, and make sure to replace the `servers` configuration with your own. + +```yaml +name: order-pipeline +kind: Pipeline +flow: +- filter: order-service +filters: +- name: order-service + kind: Proxy + pools: + - servers: + - url: http://0.0.0.0:5003 # order service +``` + +Then update the order pipeline with the command: + +```bash +egctl apply -f order-service.yaml +``` + +Save the following YAML code to `notify-service.yaml`, and make sure to replace the `servers` configuration with your own. + +```yaml +name: notify-pipeline +kind: Pipeline +flow: +- filter: notify-service +filters: +- name: notify-service + kind: Proxy + pools: + - servers: + - url: http://0.0.0.0:5002 # new version + filter: + headers: + x-ua-device: + exact: Mac # the traffic from Mac device should be directed to the new version + - servers: + - url: http://0.0.0.0:5001 # old version +``` + +Then create the notify pipeline with the command: + +```bash +egctl create -f notify-service.yaml +``` + +Send a request to e-Commerce service: + +```bash +curl http://127.0.0.1:10080/ecommerce -H "user-agent:Mozilla/5.0 (Linux; Android 4.4.2; GT-I9505 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.117 Mobile Safari/537.36" +{"order_id":"5245000","role_id":"44312","order_status":1,"order_version":"v2","notify_status":1,"notify_version":"v1"} + +curl http://127.0.0.1:10080/ecommerce -H "user-agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36" +{"order_id":"5245000","role_id":"44312","order_status":1,"order_version":"v2","notify_status":1,"notify_version":"v2"} +``` + +## based on user OS + +![case 3](../imgs/canary-release-case-3.png) + +Suppose both the Order service and Notify service have been upgraded simultaneously. The user who is accessing our service from a MacOS should be routed to the new version of the Order service (v2), and from Mac device should be routed to the new version of the Notify service (v2). + +You may think Cloudflare's Workers is not suitable for your needs, there is another easy way to parse `User-Agent` with Easegress's WASM. + +Download the latest Service code from [canary-case-3](https://github.com/megaease/easegress-canary-example/tree/main/case3) + +**1. Traffic tagging** + +Using Easegress's WASM to parse the `User-Agent`. + +Download `easegress-rust-uaparser` + +```bash +git clone https://github.com/megaease/easegress-rust-uaparser.git +``` + +Go to the `easegress-rust-uaparser/binary` directory and just use the `easegress.wasm` file directly. It will parse the `User-Agent` and add the `x-ua-device` and `x-ua-os` headers to the HTTP request. + +**2. Traffic scheduling rules** + +Save the following YAML code to `order-service.yaml`, and make sure to replace the `servers` configuration and wasm file path with your own. + +```yaml +name: order-pipeline +kind: Pipeline +flow: +- filter: wasm +- filter: order-service +filters: +- name: wasm # parse user-agent, add `x-ua-device` and `x-ua-os` headers to the HTTP request. + kind: WasmHost + maxConcurrency: 2 + code: //easegress.wasm # easegress.wasm file path + timeout: 100ms +- name: order-service + kind: Proxy + pools: + - servers: + - url: http://0.0.0.0:5002 # new version of order service + filter: + headers: + x-ua-os: # Easegress's user OS header + exact: MacOS # the traffic from MacOS should be directed to the new version + - servers: + - url: http://0.0.0.0:5001 # old version of order service +``` + +Then update the order pipeline with the command: + +```bash +egctl apply -f order-service.yaml +``` + +Send a request to e-Commerce service: + +```bash +curl http://127.0.0.1:10080/ecommerce -H "user-agent:Mozilla/5.0 (Linux; Android 4.4.2; GT-I9505 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.117 Mobile Safari/537.36" +{"order_id":"5245000","role_id":"44312","order_status":1,"order_version":"v1","notify_status":1,"notify_version":"v1"} + +curl http://127.0.0.1:10080/ecommerce -H "user-agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36" +{"order_id":"5245000","role_id":"44312","order_status":1,"order_version":"v2","notify_status":1,"notify_version":"v2"} +``` diff --git a/docs/03.Advanced-Cookbook/3.05.Distributed-Tracing.md b/docs/03.Advanced-Cookbook/3.05.Distributed-Tracing.md index 3531a36991..c0ce3fcb72 100644 --- a/docs/03.Advanced-Cookbook/3.05.Distributed-Tracing.md +++ b/docs/03.Advanced-Cookbook/3.05.Distributed-Tracing.md @@ -1,3 +1,133 @@ -TODO: +# Distributed Tracing -from distributed.md \ No newline at end of file +Easegress tracing is based on [OpenTelemetry](https://opentelemetry.io/). We can enable tracing in Traffic Gates, for example, in `HTTPServer`, we can do this by defining the `tracing` entry. Tracing creates spans containing the tracing service name (`tracing.serviceName`) and other information. The matched pipeline will start a child span, and its internal filters will start children spans according to their implementation and configuration. For example, the `Proxy` filter has a specific span implementation. + +```yaml +kind: HTTPServer +name: http-server-example +port: 10080 +tracing: + serviceName: httpServerExample + sampleRate: 1 + exporter: + zipkin: + endpoint: http://localhost:9412/api/v2/spans +rules: + - paths: + - pathPrefix: /pipeline + backend: pipeline-example +``` + +## Custom attributes + +Custom attributes can help to further filter and debug tracing spans. Here's an example with custom attribute `customAttributeKey` with value `customAttributeValue`: + +```yaml +kind: HTTPServer +name: http-server-example +port: 10080 +tracing: + serviceName: httpServerExample + attributes: # add "attributes" entry and tags as key-value pairs + customAttributeKey: customAttributeValue + sampleRate: 1 + exporter: + zipkin: + endpoint: http://localhost:9412/api/v2/spans +rules: + - paths: + - pathPrefix: /pipeline + backend: pipeline-example +``` + +## Exporter + +In the above example, we can see that we are sending span to Zipkin, thanks to the standardization of OpenTelemetry, we can also send span to other tracing backend, which currently supports Jaeger, Zipkin, OTLP (OpenTelemetry Collector), you can refer to [tracing](../reference/controllers.md#tracingspec) for details. + +![exporter](../imgs/tracing-exporter.png) + +You can configure more than one exporter at a time, so let's look at an example to understand. + +```yaml +kind: HTTPServer +name: http-server-example +port: 10080 +tracing: + serviceName: httpServerExample + sampleRate: 1 + exporter: + zipkin: + endpoint: http://localhost:9412/api/v2/spans + jaeger: + mode: agent + endpoint: localhost:6831 + otlp: + protocol: grpc + endpoint: localhost:4317 + insecure: true +rules: + - paths: + - pathPrefix: /pipeline + backend: pipeline-example +``` + +## Backward Compatibility + +In the current version, you can still use the same configuration as before, but there are still some minor differences that need to be adjusted, as you can see in the following example. + +```yaml +kind: HTTPServer +name: http-server-example +port: 10080 +tracing: + serviceName: httpServerExample + tags: # Deprecated: This option will be kept until the next major version incremented release. + customTagKey: customTagValue + zipkin: + hostport: 0.0.0.0:10080 # This option will no longer be used + serverURL: http://localhost:9412/api/v2/spans + sampleRate: 1 + sameSpan: true # This option will no longer be used + id128Bit: false # # This option will no longer be used +rules: + - paths: + - pathPrefix: /pipeline + backend: pipeline-example +``` + +The adjusted example should look like this. + +```yaml +kind: HTTPServer +name: http-server-example +port: 10080 +tracing: + serviceName: httpServerExample + tags: # Deprecated: This option will be kept until the next major version incremented release. + customTagKey: customTagValue + zipkin: + serverURL: http://localhost:9412/api/v2/spans + sampleRate: 1 +rules: + - paths: + - pathPrefix: /pipeline + backend: pipeline-example +``` + +## Usage with Cloudflare + +If a request comes from Cloudflare, the HttpServer span will have a `cf.ray` tag with the value of the Cloudflare RayID automatically. + +You can also have a Cloudflare span over the HttpServer span. + +![cloudflare-span](../imgs/tracing-cloudflare-span.png) + +To achieve that you need to add headers of the timestamps when the traffic enters Cloudflare. + +1. Go to your website in Cloudflare dashboard. +2. Go to Rules -> Transform Rules -> Modify Request Header then create a rule. +3. Add two dynamic headers: + - "x-ts-msec":"http.request.timestamp.msec" + - "x-ts-sec":"http.request.timestamp.sec" + +![cloudflare-transform-rule-header](../imgs/tracing-cloudflare-transform-header.png) diff --git a/docs/03.Advanced-Cookbook/3.07.WasmHost.md b/docs/03.Advanced-Cookbook/3.07.WasmHost.md index 1edcacab88..727406bb2c 100644 --- a/docs/03.Advanced-Cookbook/3.07.WasmHost.md +++ b/docs/03.Advanced-Cookbook/3.07.WasmHost.md @@ -1,3 +1,456 @@ -TODO: +# WasmHost -from wasmhost.md \ No newline at end of file +- [WasmHost](#wasmhost) + - [Why Use WasmHost](#why-use-wasmhost) + - [Examples](#examples) + - [Basic: Noop](#basic-noop) + - [Add a New Header](#add-a-new-header) + - [Add a New Header According to Configuration](#add-a-new-header-according-to-configuration) + - [Add a Cookie](#add-a-cookie) + - [Mock Response](#mock-response) + - [Access Shared Data](#access-shared-data) + - [Return a Result Other Than 0](#return-a-result-other-than-0) + +The `WasmHost` is a filter of Easegress which can be orchestrated into a pipeline. But while the behavior of all other filters are defined by filter developers and can only be fine-tuned by configuration, this filter implements a host environment for user-developed [WebAssembly](https://webassembly.org/) code, which enables users to control the filter behavior completely. + +## Why Use WasmHost + +* **Zero Down Time**: filter behavior can be modified by a hot update. +* **Fast Develop, Fast Deploy**: we believe everyone can be a software developer, and you know your requirement better, no need to wait for MegaEase. +* **Every Thing Under Control**: the filter behavior is right under your fingers. +* **Develop with Your Favour Language**: choose one from `AssemblyScript`, `Go`, `C/C++`, `Rust`, and so on at your wish (Note: a language-specific SDK is required, we are working on this). + +## Examples + +**Note**: The `WasmHost` filter is disabled by default, to enable it, you need to build Easegress with the below command: + +```bash +$ make build_server GOTAGS=wasmhost +``` + +We will use [AssemblyScript](https://www.assemblyscript.org/) as the language of the examples, please ensure a recent version of [Git](https://git-scm.com/), [Golang](https://golang.org), [Node.js](https://nodejs.org/) and its package manager [npm](https://www.npmjs.com/) are installed before continue. Basic knowledge about writing and working with TypeScript modules, which is very similar to AssemblyScript, is a plus. + +### Basic: Noop + +The AssemblyScript code of this example is just a noop. But this example includes all the required steps to write, build and deploy WebAssembly code, and it shows the basic structure of the code. All latter examples are based on this one. + +1. Clone git repository `easegress-assemblyscript-sdk` to someware on disk + + ```bash + $ git clone https://github.com/megaease/easegress-assemblyscript-sdk.git + ``` + +2. Switch to a new directory and initialize a new node module: + + ```bash + npm init + ``` + +3. Install the AssemblyScript compiler using npm, assume that the compiler is not required in production and make it a development dependency: + + ```bash + npm install --save-dev assemblyscript + ``` + +4. Once installed, the compiler provides a handy scaffolding utility to quickly set up a new AssemblyScript project, for example in the directory of the just initialized node module: + + ```bash + npx asinit . + ``` + +5. Add `--use abort=` to the `asc` in `package.json`, for example: + + ```json + "asbuild:untouched": "asc assembly/index.ts --target debug --use abort=", + "asbuild:optimized": "asc assembly/index.ts --target release --use abort=", + ``` + +6. Replace the content of `assembly/index.ts` with the code below, note to replace `{EASEGRESS_SDK_PATH}` with the path in step 1: + + ```typescript + // this line exports everything required by Easegress, + export * from '{EASEGRESS_SDK_PATH}/easegress/proxy' + + // import everything you need from the SDK, + import { Program, registerProgramFactory } from '{EASEGRESS_SDK_PATH}/easegress' + + // define the program, 'Noop' is the name + class Noop extends Program { + // constructor is the initializer of the program, will be called once at the startup + constructor(params: Map) { + super(params) + } + + // run will be called for every request + run(): i32 { + return 0 + } + } + + // register a factory method of the program, the only thing you + // may want to change is the program name, here is 'Noop' + registerProgramFactory((params: Map) => { + return new Noop(params) + }) + ``` + +7. Build with the below command, if everything is right, `untouched.wasm` (the debug version) and `optimized.wasm` (the release version) will be generated at the `build` folder. + + ```bash + $ npm run asbuild + ``` + +8. Create an HTTPServer in Easegress to listen on port 10080 to handle the HTTP traffic: + + ```bash + $ echo ' + kind: HTTPServer + name: server-demo + port: 10080 + keepAlive: true + https: false + rules: + - paths: + - pathPrefix: /pipeline + backend: wasm-pipeline' | egctl create -f - + ``` + +9. Create pipeline `wasm-pipeline` which includes a `WasmHost` filter: + + ```bash + $ echo ' + name: wasm-pipeline + kind: Pipeline + flow: + - filter: wasm + - filter: proxy + + filters: + - name: wasm + kind: WasmHost + maxConcurrency: 2 + code: /home/megaease/example/build/optimized.wasm + timeout: 100ms + - name: proxy + kind: Proxy + pools: + - servers: + - url: http://127.0.0.1:9095 + loadBalance: + policy: roundRobin' | egctl create -f - + ``` + + Note to replace `/home/megaease/example/build/optimized.wasm` with the path of the file generated in step 7. + +10. Set up a backend service at `http://127.0.0.1:9095` with the `mirror` server from the Easegress repository. Using another HTTP server is fine, but the `mirror` server prints requests it received to the console, which makes it much easier to verify the results: + + ```bash + $ git clone https://github.com/megaease/easegress.git + $ cd easegress + $ go run example/backend-service/mirror/mirror.go + ``` + +11. Execute below command in a new console to verify what we have done. + + ```bash + $ curl http://127.0.0.1:10080/pipeline -d 'Hello, Easegress' + Your Request + ============== + Method: POST + URL : /pipeline + Header: + User-Agent: [curl/7.68.0] + Accept: [*/*] + Content-Type: [application/x-www-form-urlencoded] + Accept-Encoding: [gzip] + Body : Hello, Easegress + ``` + +### Add a New Header + +```typescript +export * from '{EASEGRESS_SDK_PATH}/easegress/proxy' +import { Program, request, registerProgramFactory } from '{EASEGRESS_SDK_PATH}/easegress' + +class AddHeader extends Program { + run(): i32 { + request.addHeader('Wasm-Added', 'I was added by WebAssembly') + return 0 + } +} + +registerProgramFactory((params: Map) => { + return new AddHeader(params) +}) +``` + +Build and verify with: + +```bash +$ npm run asbuild +$ egctl wasm apply-data --reload-code +$ curl http://127.0.0.1:10080/pipeline -d 'Hello, Easegress' +Your Request +============== +Method: POST +URL : /pipeline +Header: + Accept-Encoding: [gzip] + User-Agent: [curl/7.68.0] + Accept: [*/*] + Content-Type: [application/x-www-form-urlencoded] + Wasm-Added: [I was added by WebAssembly] +Body : Hello, Easegress +``` + +### Add a New Header According to Configuration + +```typescript +export * from '{EASEGRESS_SDK_PATH}/easegress/proxy' +import { Program, request, registerProgramFactory } from '{EASEGRESS_SDK_PATH}/easegress' + +class AddHeader extends Program { + headerName: string + headerValue: string + + constructor(params: Map) { + this.headerName = params.get("headerName") + this.headerValue = params.get("headerValue") + super(params) + } + + run(): i32 { + request.addHeader(this.headerName, this.headerValue) + return 0 + } +} + +registerProgramFactory((params: Map) => { + return new AddHeader(params) +}) +``` + +And we also need to modify the filter configuration to add `headerName` and `headerValue` as parameters: + +```yaml +filters: + - name: wasm + kind: WasmHost + parameters: # + + headerName: "Wasm-Added-2" # + + headerValue: "I was added by WebAssembly too" # + +``` + +Build and verify with: + +```bash +$ npm run asbuild +$ egctl wasm apply-data --reload-code +$ curl http://127.0.0.1:10080/pipeline -d 'Hello, Easegress' +Your Request +============== +Method: POST +URL : /pipeline +Header: + Accept-Encoding: [gzip] + User-Agent: [curl/7.68.0] + Accept: [*/*] + Content-Type: [application/x-www-form-urlencoded] + Wasm-Added-2: [I was added by WebAssembly too] +``` + +### Add a Cookie + +```typescript +export * from '{EASEGRESS_SDK_PATH}/easegress/proxy' +import { Program, cookie, response, registerProgramFactory } from '{EASEGRESS_SDK_PATH}/easegress' + +class AddCookie extends Program { + run(): i32 { + let c = new cookie.Cookie() + c.name = "wasm" + c.value = "2021-07-29" + c.httpOnly = true + request.addCookie(c) + return 0 + } +} + +registerProgramFactory((params: Map) => { + return new AddCookie(params) +}) +``` + +Build and verify with: + +```bash +$ npm run asbuild +$ egctl wasm apply-data --reload-code +$ curl http://127.0.0.1:10080/pipeline -d 'Hello, Easegress' +Your Request +============== +Method: POST +URL : /pipeline +Header: + User-Agent: [curl/7.68.0] + Accept: [*/*] + Content-Type: [application/x-www-form-urlencoded] + Cookie: [wasm=2021-07-29; HttpOnly=] + Accept-Encoding: [gzip] +Body : Hello, Easegress +``` + +### Mock Response + +```typescript +export * from '{EASEGRESS_SDK_PATH}/easegress/proxy' +import { Program, response, registerProgramFactory } from '{EASEGRESS_SDK_PATH}/easegress' + +class MockResponse extends Program { + run(): i32 { + response.setStatusCode(200) + response.setBody(String.UTF8.encode("I have a new body now")) + return 0 + } +} + +registerProgramFactory((params: Map) => { + return new MockResponse(params) +}) +``` + +Because we are mocking a response, we need to remove the `proxy` from pipeline: + +```bash +$ echo ' +name: wasm-pipeline +kind: Pipeline +flow: +- filter: wasm +filters: +- name: wasm + kind: WasmHost + maxConcurrency: 2 + code: /home/megaease/example/build/optimized.wasm + timeout: 100ms' | egctl apply -f - +``` + +Build and verify with: + +```bash +$ npm run asbuild +$ egctl wasm reload-code +$ curl http://127.0.0.1:10080/pipeline -d 'Hello, Easegress' +I have a new body now +``` + + +### Access Shared Data + +When the `maxConcurrency` field of a WasmHost filter is larger than `1`, or when Easegress is deployed as a cluster, a single WasmHost filter could have more than one `Wasm Virtual Machine`s. Because safety is the design principle of WebAssembly, these VMs are isolated and can not share data with each other. + +But sometimes, sharing data is useful, Easegress provides APIs for this: + +```typescript +export * from '{EASEGRESS_SDK_PATH}/easegress/proxy' +import { Program, response, cluster, registerProgramFactory } from '{EASEGRESS_SDK_PATH}/easegress' + +class SharedData extends Program { + run(): i32 { + let counter = cluster.AddInteger("counter", 1) + response.setStatusCode(200) + response.setBody(String.UTF8.encode("number of requests is: ", counter.toString())) + return 0 + } +} + +registerProgramFactory((params: Map) => { + return new SharedData(params) +}) +``` + +Suppose we are using the same pipeline configuration as in [Mock Response](#mock-response), we can build and verify this example with: + +```bash +$ npm run asbuild +$ egctl wasm reload-code +$ curl http://127.0.0.1:10080/pipeline +number of requests is 1 +$ curl http://127.0.0.1:10080/pipeline +number of requests is 2 +$ curl http://127.0.0.1:10080/pipeline +number of requests is 3 +``` + +We can view the shared data with: + +```bash +$ egctl wasm list-data wasm-pipeline wasm +counter: "3" +``` + +where `wasm-pipeline` is the pipeline name and `wasm` is the filter name. + +The shared data can be modified with: + +```bash +$ echo 'counter: 100' | egctl wasm apply-data wasm-pipeline wasm +$ curl http://127.0.0.1:10080/pipeline +number of requests is 101 +``` + +And can be deleted with: + +```bash +$ egctl wasm delete-data wasm-pipeline wasm +``` + +### Return a Result Other Than 0 + +```typescript +export * from '{EASEGRESS_SDK_PATH}/easegress/proxy' +import { Program, request, registerProgramFactory } from '{EASEGRESS_SDK_PATH}/easegress' + +class NonZeroResult extends Program { + run(): i32 { + let token = request.getHeader("Authorization") + if(token == "") { + return 1 + } + return 0 + } +} + +registerProgramFactory((params: Map) => { + return new NonZeroResult(params) +}) +``` + +Return value `1` will be converted to `wasmResult1` by the `WasmHost` filter, we can use this result in the pipeline configuration: + +```yaml +flow: + - filter: wasm + jumpIf: { wasmResult1: END } # + + - filter: proxy +``` + +Build and verify with: + +```bash +$ npm run asbuild +$ egctl wasm reload-code +$ curl http://127.0.0.1:10080/pipeline -d 'Hello, Easegress' +$ curl http://127.0.0.1:10080/pipeline -d 'Hello, Easegress' -HAuthorization:abc +Your Request +============== +Method: POST +URL : /pipeline +Header: + Accept-Encoding: [gzip] + User-Agent: [curl/7.68.0] + Accept: [*/*] + Authorization: [abc] + Content-Type: [application/x-www-form-urlencoded] +Body : Hello, Easegress +``` diff --git a/docs/03.Advanced-Cookbook/README.md b/docs/03.Advanced-Cookbook/README.md index bcb3c323c8..06a68501dc 100644 --- a/docs/03.Advanced-Cookbook/README.md +++ b/docs/03.Advanced-Cookbook/README.md @@ -2,7 +2,7 @@ - [MQTT Proxy](3.01.MQTT-Proxy.md) - [Flash Sale](3.02.Flash-Sale.md) -- [Multiple API Orchestration](3.03.Telegram-Translation-Bot.md) +- [Multiple API Orchestration](3.03.Multiple-API-Orchestration.md) - [Canary Release](3.04.Canary-Release.md) - [Distributed Tracing](3.05.Distributed-Tracing.md) - [Service Proxy](3.06.Service-Proxy.md) diff --git a/docs/07.Reference/7.1.Controllers.md b/docs/07.Reference/7.1.Controllers.md new file mode 100644 index 0000000000..f49b3fea46 --- /dev/null +++ b/docs/07.Reference/7.1.Controllers.md @@ -0,0 +1,819 @@ +# Controllers + +- [Controllers](#controllers) + - [System Controllers](#system-controllers) + - [ServiceRegistry](#serviceregistry) + - [TrafficController](#trafficcontroller) + - [RawConfigTrafficController](#rawconfigtrafficcontroller) + - [HTTPServer](#httpserver) + - [gRRC Service Proxy](#grrc-service-proxy) + - [Simple Proxy Configuration](#simple-proxy-configuration) + - [Configuration](#configuration) + - [AccessLogVariable](#accesslogvariable) + - [Pipeline](#pipeline) + - [StatusSyncController](#statussynccontroller) + - [Business Controllers](#business-controllers) + - [GlobalFilter](#globalfilter) + - [EaseMonitorMetrics](#easemonitormetrics) + - [FaaSController](#faascontroller) + - [IngressController](#ingresscontroller) + - [ConsulServiceRegistry](#consulserviceregistry) + - [EtcdServiceRegistry](#etcdserviceregistry) + - [EurekaServiceRegistry](#eurekaserviceregistry) + - [ZookeeperServiceRegistry](#zookeeperserviceregistry) + - [NacosServiceRegistry](#nacosserviceregistry) + - [AutoCertManager](#autocertmanager) + - [Common Types](#common-types) + - [tracing.Spec](#tracingspec) + - [spanlimits.Spec](#spanlimitsspec) + - [batchlimits.Spec](#batchlimitsspec) + - [exporter.Spec](#exporterspec) + - [jaeger.Spec](#jaegerspec) + - [zipkin.Spec](#zipkinspec) + - [otlp.Spec](#otlpspec) + - [zipkin.DeprecatedSpec](#zipkindeprecatedspec) + - [ipfilter.Spec](#ipfilterspec) + - [httpserver.Rule](#httpserverrule) + - [httpserver.Host](#httpserverhost) + - [httpserver.Path](#httpserverpath) + - [httpserver.Header](#httpserverheader) + - [pipeline.Spec](#pipelinespec) + - [pipeline.FlowNode](#pipelineflownode) + - [filters.Filter](#filtersfilter) + - [easemonitormetrics.Kafka](#easemonitormetricskafka) + - [nacos.ServerSpec](#nacosserverspec) + - [autocertmanager.DomainSpec](#autocertmanagerdomainspec) + - [resilience.Policy](#resiliencepolicy) + - [Retry Policy](#retry-policy) + - [CircuitBreaker Policy](#circuitbreaker-policy) + +As the [architecture diagram](../imgs/architecture.png) shows, the controller is the core entity to control kinds of working. There are two kinds of controllers overall: + +- System Controller: It is created one and only one instance in every Easegress node, which can't be deleted. They mainly aim to control essential system-level stuff. +- Business Controller: It could be created, updated, deleted by admin operation. They control various resources such as mesh traffic, service discovery, faas, and so on. + +In another view, Easegress as a traffic orchestration system, we could classify them into traffic controller and non-traffic controller: + +- Traffic Controller: It invokes TrafficController to handle its specific traffic, such as MeshController. +- Non-Traffic Controller: It doesn't handle business traffic, such as EurekaServiceRegistry, even though it has admin traffic with Eureka. + +The two categories are conceptual, which means they are not strict distinctions. We just use them as terms to clarify controllers technically. + +## System Controllers + +For now, all system controllers can not be configured. It may gain this capability if necessary in the future. + +### ServiceRegistry + +We use the system controller `ServiceRegistry` as the service hub for all service registries. Current drivers are + +- [ConsulServiceRegistry](#consulserviceregistry) +- [EtcdServiceRegistry](#etcdserviceregistry) +- [EurekaServiceRegistry](#eurekaserviceregistry) +- [ZookeeperServiceRegistry](#zookeeperserviceregistry) +- [NacosServiceRegistry](#nacosserviceregistry) + +The drivers need to offer notifying change periodically, and operations to the external service registry. + +### TrafficController + +TrafficController handles the lifecycle of Traffic Gates (like HTTPServer) and Pipeline and their relationship. It manages the resource in a namespaced way. Traffic gates accepts incoming traffic and routes it to Pipelines in the same namespace. Most other controllers could handle traffic by leverage the ability of TrafficController.. + +### RawConfigTrafficController + +RawConfigTrafficController maps all traffic static configurations to TrafficController in the namespace `default`. We could use `egctl` to manage the configuration of servers and pipelines in the default namespace. + +#### HTTPServer + +HTTPServer is a server that listens on one port to route all traffic to available pipelines. Its simplest config looks like: + +```yaml +kind: HTTPServer +name: http-server-example +port: 80 +rules: + - paths: + - pathPrefix: /pipeline + backend: http-pipeline-example +``` + +| Name | Type | Description | Required | +| ---------------- | ---------------------------------- | ---------------------------------------------------------------------------------------- | -------------------- | +| http3 | bool | Whether to support HTTP3(QUIC) | No | +| port | uint16 | The HTTP port listening on | Yes | +| keepAlive | bool | Whether to support keepalive | Yes (default: false) | +| keepAliveTimeout | string | The timeout of keepalive | Yes (default: 60s) | +| maxConnections | uint32 | The max connections with clients | Yes (default: 10240) | +| https | bool | Whether to use HTTPS | Yes (default: false) | +| cacheSize | uint32 | The size of cache, 0 means no cache | No | +| xForwardedFor | bool | Whether to set X-Forwarded-For header by own ip | No | +| tracing | [tracing.Spec](#tracingSpec) | Distributed tracing settings | No | +| certBase64 | string | Public key of PEM encoded data in base64 encoded format | No | +| keyBase64 | string | Private key of PEM encoded data in base64 encoded format | No | +| certs | map[string]string | Public keys of PEM encoded data, the key is the logic pair name, which must match keys | No | +| keys | map[string]string | Private keys of PEM encoded data, the key is the logic pair name, which must match certs | No | +| ipFilter | [ipfilter.Spec](#ipfilterSpec) | IP Filter for all traffic under the server | No | +| routerKind | string | Kind of router. see [routers](./routers.md) | No (default: Order) | +| rules | [][httpserver.Rule](#httpserverrule) | Router rules | No | +| autoCert | bool | Do HTTP certification automatically | No | +| clientMaxBodySize | int64 | Max size of request body. the default value is 4MB. Requests with a body larger than this option are discarded. When this option is set to `-1`, Easegress takes the request body as a stream and the body can be any size, but some features are not possible in this case, please refer [Stream](./stream.md) for more information. | No | +| caCertBase64 | string | Define the root certificate authorities that servers use if required to verify a client certificate by the policy in TLS Client Authentication. | No | +| globalFilter | string | Name of [GlobalFilter](#globalfilter) for all backends | No | +| accessLogFormat | string | Format of access log, default is `[{{Time}}] [{{RemoteAddr}} {{RealIP}} {{Method}} {{URI}} {{Proto}} {{StatusCode}}] [{{Duration}} rx:{{ReqSize}}B tx:{{RespSize}}B] [{{Tags}}]`, variable is delimited by "{{" and "}}", please refer [Access Log Variable](#accesslogvariable) for all built-in variables | No | + +#### gRRC Service Proxy + +Easegress gRPC servers is a proxy server base on gRPC protocol. + +##### Simple Proxy Configuration +Below is one of the simplest gRPC Proxy Server, and it will listen on port `8080` and handle the requests that includes kv `content-type:application/grpc` in headers by use backend `pipeline-grpc-forward` +``` yaml +kind: GRPCServer +port: 8080 +name: server-grpc +rules: + - methods: + - backend: pipeline-grpc + headers: + - key: "Content-Type" + values: + - "application/grpc" +xForwardedFor: true +``` + +##### Configuration +The below parameters will help manage connections better + +| Name | Type | Description | Required | +|------|------|-------------|----------| +| maxConnections | uint32 | The maximum number of connections allowed by gRPC Server , default value 10240, min is 1 | No | +| minTimeClientSendPing | duration | The minimum amount of time a client should wait before sending a keepalive ping, default value is 5 minutes | No | +| permitClintSendPingWithoutStream | duration | If true, server allows keepalive pings even when there are no active streams(RPCs). If false, and client sends ping when there are no active streams, server will send GOAWAY and close the connection. default false | No | +| maxConnectionIdle | duration | A duration for the amount of time after which an idle connection would be closed by sending a GoAway. Idleness duration is defined since the most recent time the number of outstanding RPCs became zero or the connection establishment. default value is infinity | No | +| maxConnectionAge | duration | A duration for the maximum amount of time a connection may exist before it will be closed by sending a GoAway. A random jitter of ±10% will be added to MaxConnectionAge to prevent connection storms. default value is infinity | No | +| maxConnectionAgeGrace | duration | An additive period after MaxConnectionAge after which the connection will be forcibly closed. default value is infinity | No | +| keepaliveTime | duration | After a duration of this time if the server doesn't see any activity it pings the client to see if the transport is still alive. If set below 1s, a minimum value of 1s will be used instead. default value is 2 hours. | No | +| keepaliveTimeout | duration | After having pinged for keepalive check, the server waits for a duration of Timeout and if no activity is seen even after that the connection is closed. default value is 20 seconds |No | + + +### AccessLogVariable + +| Name | Description | +| ---------------- | ----------------------------------------------------------------- | +| Time | Start time for handling the request +| RemoteAddr | Network address that sent the request +| RealIP | Real IP of the request +| Method | HTTP method (GET, POST, PUT, etc.) for the request +| URI | Unmodified request-target of the Request-Line +| Proto | Protocol version for the request +| StatusCode | HTTP status code for the response +| Duration | Duration time for handing the request +| ReqSize | Size read from the request +| RespSize | Size write to the response +| ReqHeaders | Request HTTP headers +| RespHeaders | Response HTTP headers +| Tags | Tags for handing the request + +#### Pipeline + +Pipeline is used to orchestrate filters. Its simplest config looks like: + +```yaml +name: http-pipeline-example1 +kind: Pipeline +flow: +- filter: proxy + +filters: +- name: proxy + kind: Proxy + pools: + - servers: + - url: http://127.0.0.1:9095 +``` + +The `flow` defines the execution order of filters. You can use `jumpIf` to +change the order. + +For example, if a request’s header doesn’t have the key `X-Id` or its value +is not `user1` or `user2`, then the `validator` filter returns result `invalid` +and the pipeline jumps to `END`. + +```yaml +name: http-pipeline-example2 +kind: Pipeline +flow: +- filter: validator + jumpIf: + # END is a built-in filter, it stops the execution of the pipeline. + invalid: END +- filter: proxy + +filters: +- name: validator + kind: Validator + headers: + X-Id: + values: ["user1", "user2"] +- name: proxy + kind: Proxy + pools: + - servers: + - url: http://127.0.0.1:9095 +``` + +> `jumpIf` can only jump to filters behind the current filter. + +The `resilience` field defines resilience policies, if a filter implements the `filters.Resiliencer` interface (for now, only the `Proxy` filter implements the interface), the pipeline injects the policies into the filter instance after creating it. +A filter can implement the `filters.Resiliencer` interface to support resilience. There are two kinds of resilience, `Retry` and `CircuitBreaker`. Check [resilience](#resilience) for more details. The following config adds a retry policy to the proxy filter: + +```yaml +name: http-pipeline-example3 +kind: Pipeline +flow: +- filter: proxy + +filters: +- name: proxy + kind: Proxy + pools: + - servers: + - url: http://127.0.0.1:9095 + retryPolicy: retry + +resilience: +- name: retry + kind: Retry + maxAttempts: 3 +``` + +In this case, if `proxy` returns non-empty results, then resilience retry reruns the `proxy` filter until `proxy` returns empty results or gets the max attempts. + +The `flow` also supports `namespace`, so the pipeline can support workflows that contain multiple requests and responses. + +```yaml +name: http-pipeline-example4 +kind: Pipeline +flow: +- filter: validator + jumpIf: + invalid: END +- filter: requestBuilderFoo + namespace: foo +- filter: proxyFoo + namespace: foo +- filter: requestBuilderBar + namespace: bar +- filter: proxyBar + namespace: bar +- filter: responseBuilder + +filters: +- name: requestBuilder + kind: RequestBuilder + ... +... +``` + +In this case, `requestBuilderFoo` creates a request in namespace `foo`, and `proxyFoo` sends `foo` request and puts the response into namespace `foo`. `requestBuilderBar` creates a request in namespace `bar` and `proxyBar` sends `bar` request and puts the response into namespace `bar`. Finally, `requestBuilder` creates a response and puts it into the default namespace. + +> If not set, the filter works in the default namespace `DEFAULT`. + +The `alias` in `flow` gives a filter an alias to help re-use the filter so that we can use the alias to distinguish each of its appearances in the flow. + +```yaml +name: http-pipeline-example5 +kind: Pipeline +flow: +- filter: validator + jumpIf: + invalid: proxy2 +- filter: proxy +# when meeting filter END, the pipeline execution stops and returns. +- filter: END +- filter: proxy + alias: proxy2 +- filter: responseAdaptor + +filters: +- name: proxy + kind: Proxy + ... +``` + +In this case, we give second `proxy` alias `proxy2`, so request is invalid, it jumps to second proxy. + +The `data` field defines static user data for the pipeline, which can be +accessed by filters. For example, in the below pipeline, the body of the result +request of the RequestBuilder will be `hello world`, which is the value of +data item `foo`. + +```yaml +name: http-pipeline-example6 +kind: Pipeline +flow: + ... + +filters: +- name: requestBuilder + kind: RequestBuilder + template: | + body: {{.data.PIPELINE.foo}} + +data: + foo: "hello world" +``` + +| Name | Type | Description | Required | +| ------------- | -------- | -------------- | -------------------- | +| flow | [][FlowNode](#pipelineflownode) | The execution order of filters, if empty, will use the order of the filter definitions. | No | +| filters | []map[string]interface{} | Defines filters, please refer [Filters](filters.md) for details of a specific filter kind. | Yes | +| resilience | []map[string]interface{} | Defines resilience policies, please refer [Resilience Policy](#resiliencepolicy) for details of a specific resilience policy. | No | +| data | map[string]interface{} | Static user data of the pipeline. | No | + + +### StatusSyncController + +No config. + +## Business Controllers + +### GlobalFilter + +`GlobalFilter` is a special pipeline that can be executed before or/and after all pipelines in a server. For example: + +```yaml +name: globalFilter-example +kind: GlobalFilter +beforePipeline: + flow: + - filter: validator + + filters: + - name: validator + kind: Validator + ... +--- +name: server-example +kind: HTTPServer +globalFilter: globalFilter-example +... +``` + +In this case, all requests in HTTPServer `server-example` go through GlobalFilter `globalFilter-example` before executing any other pipelines. + +| Name | Type | Description | Required | +|------|------|-------------|----------| +| beforePipeline | [pipeline.Spec](#pipelineSpec) | Spec for before pipeline | No | +| afterPipeline | [pipeline.Spec](#pipelinespec) | Spec for after pipeline | No | + +### EaseMonitorMetrics + +EaseMonitorMetrics is adapted to monitor metrics of Easegress and send them to Kafka. The config looks like: + +```yaml +kind: EaseMonitorMetrics +name: easemonitor-metrics-example +kafka: + brokers: ["127.0.0.1:9092"] + topic: metrics +``` + +| Name | Type | Description | Required | +| ----- | ---------------------------------------------------- | -------------------- | -------- | +| kafka | [easemonitormetrics.Kafka](#easemonitormetricsKafka) | Kafka related config | Yes | + +### FaaSController + +A FaaSController is a business controller for handling Easegress and FaaS products integration purposes. It abstracts `FaasFunction`, `FaaSStore` and, `FaasProvider`. Currently, we only support `Knative` type `FaaSProvider`. + +For the full reference document please check - [FaaS Controller](./faascontroller.md) + +### IngressController + +The IngressController is an implementation of [Kubernetes ingress controller](https://kubernetes.io/docs/concepts/services-networking/ingress-controllers/), it watches Kubernetes Ingress, Service, Endpoints, and Secrets then translates them to Easegress HTTP server and pipelines. The config looks like: + +```yaml +kind: IngressController +name: ingress-controller-example +kubeConfig: +masterURL: +namespaces: ["default"] +ingressClass: easegress +httpServer: + port: 8080 + https: false + keepAlive: true + keepAliveTimeout: 60s + maxConnections: 10240 +``` + +| Name | Type | Description | Required | +| ------------ | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | +| kubeConfig | string | Path of the Kubernetes configuration file. | No | +| masterURL | string | The address of the Kubernetes API server. | No | +| namespaces | []string | An array of Kubernetes namespaces which the IngressController needs to watch, all namespaces are watched if left empty. | No | +| ingressClass | string | The IngressController only handles `Ingresses` with `ingressClassName` set to the value of this option. | No (default: easegress) | +| httpServer | [httpserver.Spec](#httpserver) | Basic configuration for the shared HTTP traffic gate. The routing rules will be generated dynamically according to Kubernetes ingresses and should not be specified here. | Yes | + +**Note**: IngressController uses `kubeConfig` and `masterURL` to connect to Kubernetes, at least one of them must be specified when deployed outside of a Kubernetes cluster, and both are optional when deployed inside a cluster. + +### ConsulServiceRegistry + +ConsulServiceRegistry supports service discovery for Consul as backend. The config looks like: + +```yaml +kind: ConsulServiceRegistry +name: consul-service-registry-example +address: '127.0.0.1:8500' +scheme: http +syncInterval: 10s +``` + +| Name | Type | Description | Required | +| ------------ | -------- | ---------------------------- | ----------------------------- | +| address | string | Consul server address | Yes (default: 127.0.0.1:8500) | +| scheme | string | Communication scheme | Yes (default: http) | +| datacenter | string | Datacenter name | No | +| token | string | ACL token for communication | No | +| namespace | string | Namespace to use | No | +| syncInterval | string | Interval to synchronize data | Yes (default: 10s) | +| serviceTags | []string | Service tags to query | No | + +### EtcdServiceRegistry + +EtcdServiceRegistry support service discovery for Etcd as backend. The config looks like: + +```yaml +kind: EtcdServiceRegistry +name: etcd-service-registry-example +endpoints: ['127.0.0.1:12379'] +prefix: "/services/" +cacheTimeout: 10s +``` + +| Name | Type | Description | Required | +| ------------ | -------- | ------------------------------ | ------------------------- | +| endpoints | []string | Endpoints of Etcd servers | Yes | +| prefix | string | Prefix of the keys of services | Yes (default: /services/) | +| cacheTimeout | string | Timeout of cache | Yes (default: 60s) | + +### EurekaServiceRegistry + +EurekaServiceRegistry supports service discovery for Eureka as backend. The config looks like: + +```yaml +kind: EurekaServiceRegistry +name: eureka-service-registry-example +endpoints: ['http://127.0.0.1:8761/eureka'] +syncInterval: 10s +``` + +| Name | Type | Description | Required | +| ------------ | -------- | ---------------------------- | ------------------------------------------- | +| endpoints | []string | Endpoints of Eureka servers | Yes (default: ) | +| syncInterval | string | Interval to synchronize data | Yes (default: 10s) | + +### ZookeeperServiceRegistry + +ZookeeperServiceRegistry supports service discovery for Zookeeper as backend. The config looks like: + +```yaml +kind: ZookeeperServiceRegistry +name: zookeeper-service-registry-example +zkservices: [127.0.0.1:2181] +prefix: /services +conntimeout: 6s +syncInterval: 10s +``` + +| Name | Type | Description | Required | +| ------------ | -------- | ---------------------------- | ----------------------------- | +| zkservices | []string | Zookeeper service addresses | Yes (default: 127.0.0.1:2181) | +| connTimeout | string | Timeout of connection | Yes (default: 6s) | +| prefix | string | Prefix of services | Yes (default: /) | +| syncInterval | string | Interval to synchronize data | Yes (default: 10s) | + +### NacosServiceRegistry + +NacosServiceRegistry supports service discovery for Nacos as backend. The config looks like: + +```yaml +kind: NacosServiceRegistry +name: nacos-service-registry-example +syncInterval: 10s +servers: + - scheme: http + port: 8848 + contextPath: /nacos + ipAddr: 127.0.0.1 +``` + +| Name | Type | Description | Required | +| ------------ | ------------------------------------- | ---------------------------- | ------------------ | +| servers | [][nacosServerSpec](#nacosserverspec) | Servers of Nacos | Yes | +| syncInterval | string | Interval to synchronize data | Yes (default: 10s) | +| namespace | string | The namespace of Nacos | No | +| username | string | The username of client | No | +| password | string | The password of client | No | + +### AutoCertManager + +AutoCertManager automatically manage HTTPS certificates. The config looks like: + +```yaml +kind: AutoCertManager +name: autocert +email: someone@megaease.com +directoryURL: https://acme-v02.api.letsencrypt.org/directory +renewBefore: 720h +enableHTTP01: true +enableTLSALPN01: true +enableDNS01: true +domains: + - name: "*.megaease.com" + dnsProvider: + name: dnspod + zone: megaease.com + apiToken: +``` + +| Name | Type | Description | Required | +| --------------- | ------------------------------------------ | ------------------------------------------------------------------------------------ | ---------------------------------- | +| email | string | An email address for CA account | Yes | +| directoryURL | string | The endpoint of the CA directory | No (default to use Let's Encrypt) | +| renewBefore | string | A certificate will be renewed before this duration of its expire time | No (default 720 hours) | +| enableHTTP01 | bool | Enable HTTP-01 challenge (Easegress need to be accessable at port 80 when true) | No (default true) | +| enableTLSALPN01 | bool | Enable TLS-ALPN-01 challenge (Easegress need to be accessable at port 443 when true) | No (default true) | +| enableDNS01 | bool | Enable DNS-01 challenge | No (default true) | +| domains | [][DomainSpec](#autocertmanagerdomainspec) | Domains to be managed | Yes | + +## Common Types + +### tracing.Spec + +| Name | Type | Description | Required | +| ----------- | -------------------------- | ----------------------------- | -------- | +| serviceName | string | The service name of top level | Yes | +| attributes | map[string]string | Attributes to include to every span. | No | +| tags | map[string]string | Deprecated. Tags to include to every span. This option will be kept until the next major version incremented release. | No | +| spanLimits | [spanlimits.Spec](#spanlimitsSpec) | SpanLimitsSpec represents the limits of a span. | No | +| sampleRate | float64 | The sample rate for collecting metrics, the range is [0, 1]. For backward compatibility, if the exporter is empty, the default is to use zipkin.sampleRate | No (default: 1) | +| batchLimits | [batchlimits.Spec](#batchlimitsSpec) | BatchLimitsSpec describes BatchSpanProcessorOptions | No | +| exporter | [exporter.Spec](#exporterSpec) | ExporterSpec describes exporter. exporter and zipkin cannot both be empty | No | +| zipkin | [zipkin.DeprecatedSpec](#zipkinDeprecatedSpec) | ZipkinDeprecatedSpec describes Zipkin. If exporter is configured, this option does not take effect. This option will be kept until the next major version incremented release. | No | +| headerFormat | string | HeaderFormat represents which format should be used for context propagation. options: [trace-conext](https://www.w3.org/TR/trace-context/),b3. For backward compatibility, the historical Zipkin configuration remains in b3 format. | No (default: trace-conext) | + +#### spanlimits.Spec + +| Name | Type | Description | Required | +| ----------- | -------------------------- | ----------------------------- | -------- | +| attributeValueLengthLimit | int | AttributeValueLengthLimit is the maximum allowed attribute value length, Setting this to a negative value means no limit is applied| No (default:-1) | +| attributeCountLimit | int | AttributeCountLimit is the maximum allowed span attribute count| No (default:128)| +| eventCountLimit | int | EventCountLimit is the maximum allowed span event count| No (default:128)| +| linkCountLimit | int | LinkCountLimit is the maximum allowed span link count| No (default:128)| +| attributePerEventCountLimit | int | AttributePerEventCountLimit is the maximum number of attributes allowed per span event| No (default:128)| +| attributePerLinkCountLimit | int | AttributePerLinkCountLimit is the maximum number of attributes allowed per span link| No (default:128)| + +#### batchlimits.Spec + +| Name | Type | Description | Required | +| ----------- | -------------------------- | ----------------------------- | -------- | +| maxQueueSize | int |MaxQueueSize is the maximum queue size to buffer spans for delayed processing| No (default:2048) | +| batchTimeout | int | BatchTimeout is the maximum duration for constructing a batch| No (default:5000 msec)| +| exportTimeout | int | ExportTimeout specifies the maximum duration for exporting spans| No (default:30000 msec)| +| maxExportBatchSize | int | MaxExportBatchSize is the maximum number of spans to process in a single batch| No (default:512)| + +#### exporter.Spec + +| Name | Type | Description | Required | +| ----------- | -------------------------- | ----------------------------- | -------- | +| jaeger | [jaeger.Spec](#jaegerSpec) | JaegerSpec describes Jaeger | No | +| zipkin | [zipkin.Spec](#zipkinSpec) | ZipkinSpec describes Zipkin | No | +| otlp | [otlp.Spec](#otlpSpec) | OTLPSpec describes OpenTelemetry exporter | No | + +#### jaeger.Spec + +| Name | Type | Description | Required | +| ----------- | -------------------------- | ----------------------------- | -------- | +| mode | string |Jaeger's access mode | Yes (options: agent,collector) | +| endpoint | string |In agent mode, endpoint must be host:port, in collector mode it is url| No| +| username | string |The username used in collector mode| No | +| password | string | The password used in collector mode| No| + +#### zipkin.Spec + +| Name | Type | Description | Required | +|---------------|---------|----------------------------------------------------------------------------------------------------| -------- | +| endpoint | string | The zipkin server URL | Yes | + +#### otlp.Spec + +| Name | Type | Description | Required | +| ----------- | -------------------------- | ----------------------------- | -------- | +| protocol | string | Connection protocol of otlp | Yes (options: http,grpc) | +| endpoint | string | Endpoint of the otlp collector| Yes| +| insecure | bool | Whether to allow insecure connections| No (default: false)| +| compression | string |Compression describes the compression used for payloads sent to the collector| No (options: gzip) | + +#### zipkin.DeprecatedSpec + +| Name | Type | Description | Required | +|---------------|---------|----------------------------------------------------------------------------------------------------| -------- | +| ~~hostPort~~ | string | Deprecated. The host:port of the service | No | +| serverURL | string | The zipkin server URL | Yes | +| sampleRate | float64 | The sample rate for collecting metrics, the range is [0, 1] | Yes | +| ~~disableReport~~ | bool | Deprecated. Whether to report span model data to zipkin server | No | +| ~~sameSpan~~ | bool | Deprecated. Whether to allow to place client-side and server-side annotations for an RPC call in the same span | No | +| ~~id128Bit~~ | bool | Deprecated. Whether to start traces with 128-bit trace id | No | + +### ipfilter.Spec + +| Name | Type | Description | Required | +| -------------- | -------- | ---------------------------------------------------- | -------------------- | +| blockByDefault | bool | Set block is the default action if not matching | Yes (default: false) | +| allowIPs | []string | IPs to be allowed to pass (support IPv4, IPv6, CIDR) | No | +| blockIPs | []string | IPs to be blocked to pass (support IPv4, IPv6, CIDR) | No | + +### httpserver.Rule + +| Name | Type | Description | Required | +| ---------- | ----------------------------------- | ------------------------------------------------------------- | -------- | +| ipFilter | [ipfilter.Spec](#ipfilterSpec) | IP Filter for all traffic under the rule | No | +| host | string | Exact host to match | No | +| hostRegexp | string | Host in regular expression to match | No | +| hosts | [][httpserver.Host](#httpserverhost) | Hosts to match | No | +| paths | [][httpserver.Path](#httpserverPath) | Path matching rules, empty means to match nothing. Note that multiple paths are matched in the order of their appearance in the spec, this is different from Nginx. | No | + +**Note**: if `host` or `hostRegexp` is not empty, they will be added into +`hosts` at runtime, and if the result `hosts` is empty, all hosts are matched. + +### httpserver.Host + +| Name | Type | Description | Required | +| ------------- | ------------------------ | ---------------------------------------------------------------------- | -------- | +| isRegexp | bool | Whether `value` is regular expression or exact value, default is false | No | +| value | string | Host value to match | Yes | + +### httpserver.Path + +| Name | Type | Description | Required | +| ------------- | ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | -------- | +| ipFilter | [ipfilter.Spec](#ipfilterSpec) | IP Filter for all traffic under the path | No | +| path | string | Exact path to match | No | +| pathPrefix | string | Prefix of the path to match | No | +| pathRegexp | string | Path in regular expression to match | No | +| rewriteTarget | string | Use pathRegexp.[ReplaceAllString](https://golang.org/pkg/regexp/#Regexp.ReplaceAllString)(path, rewriteTarget) or pathPrefix [strings.Replace](https://pkg.go.dev/strings#Replace) to rewrite request path | No | +| methods | []string | Methods to match, empty means to allow all methods | No | +| headers | [][httpserver.Header](#httpserverHeader) | Headers to match (the requests matching headers won't be put into cache) | No | +| backend | string | backend name (pipeline name in static config, service name in mesh) | Yes | +| clientMaxBodySize | int64 | Max size of request body, will use the option of the HTTP server if not set. the default value is 4MB. Requests with a body larger than this option are discarded. When this option is set to `-1`, Easegress takes the request body as a stream and the body can be any size, but some features are not possible in this case, please refer [Stream](./stream.md) for more information. | No | +| matchAllHeader | bool | Match all headers that are defined in headers, default is `false`. | No | +| matchAllQuery | bool | Match all queries that are defined in queries, default is `false`. | No | + +### httpserver.Header + +There must be at least one of `values` and `regexp`. + +| Name | Type | Description | Required | +| ------- | -------- | ------------------------------------------------------------------- | -------- | +| key | string | Header key to match | Yes | +| values | []string | Header values to match | No | +| regexp | string | Header value in regular expression to match | No | + +### pipeline.Spec + +| Name | Type | Description | Required | +|------|------|-------------|----------| +| flow | [pipeline.FlowNode](#pipelineFlowNode) | Flow of pipeline | No | +| filters | [][filters.Filter](#filters.Filter) | Filter definitions of pipeline | Yes | +| resilience | [][resilience.Policy](#resiliencePolicy) | Resilience policy for backend filters | No | + +### pipeline.FlowNode + +| Name | Type | Description | Required | +| ------ | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | +| filter | string | The filter name | Yes | +| jumpIf | map[string]string | Jump to another filter conditionally, the key is the result of the current filter, the value is the target filter name/alias. `END` is the built-in value for the ending of the pipeline | No | +| namespace | string | Namespace of the filter | No | +| alias | string | Alias name of the filter | No | + +### filters.Filter + +The self-defining specification of each filter references to [filters](./filters.md). + +| Name | Type | Description | Required | +| ------------------------------------ | ------ | -------------- | -------- | +| name | string | Name of filter | Yes | +| kind | string | Kind of filter | Yes | +| [self-defining fields](./filters.md) | - | - | - | + +### easemonitormetrics.Kafka + +| Name | Type | Description | Required | +| ------- | -------- | ---------------- | ----------------------------- | +| brokers | []string | Broker addresses | Yes (default: localhost:9092) | +| topic | string | Produce topic | Yes | + +### nacos.ServerSpec + +| Name | Type | Description | Required | +| ----------- | ------ | -------------------------------------------- | -------- | +| ipAddr | string | The ip address | Yes | +| port | uint16 | The port | Yes | +| scheme | string | The scheme of protocol (support http, https) | No | +| contextPath | string | The context path | No | + +### autocertmanager.DomainSpec + +| Name | Type | Description | Required | +| ----------- | ----------------- | --------------------------| ------------------------------------ | +| name | string | The name of the domain | Yes | +| dnsProvider | map[string]string | DNS provider information | No (Yes if `DNS-01` chanllenge is desired) | + +The fields in `dnsProvider` vary from DNS providers, but: + +- `name` and `zone` are required for all DNS providers. +- `nsAddress` and `nsNetwork` are optional name server information for all DNS + providers, if provided, AutoCertManager will leverage them to speed up the + DNS record lookup. `nsAddress` is the address of the name server, must always + include the port number, `nsNetwork` is the network protocol of name server, + it should be `udp` in most cases. + +Below table list other required fields for each supported DNS provider (Note: `google` is temporarily disabled due to dependency conflict): + +| DNS Provider Name | Required Fields | +| ----------------- | ------------------------------------------------------------------- | +| alidns | accessKeyId, accessKeySecret | +| azure | tenantId, clientId, clientSecret, subscriptionId, resourceGroupName | +| cloudflare | apiToken | +| digitalocean | apiToken | +| dnspod | apiToken | +| duckdns | apiToken | +| google | project | +| hetzner | authApiToken | +| route53 | accessKeyId, secretAccessKey, awsProfile | +| vultr | apiToken | + +### resilience.Policy + +| Name | Type | Description | Required | +| -------------------- | ------ | -------------- | -------- | +| name | string | Name of filter | Yes | +| kind | string | Kind of filter | Yes | +| other kind specific fields of the policy kind | - | - | - | + +#### Retry Policy + +A retry policy configures how to retry a failed request. + +| Name | Type | Description | Required | +|------|------|-------------|----------| +| maxAttempts | int | The maximum number of attempts (including the initial one). Default is 3 | No | +| waitDuration | string | The base wait duration between attempts. Default is 500ms | No | +| backOffPolicy | string | The back-off policy for wait duration, could be `EXPONENTIAL` or `RANDOM` and the default is `RANDOM`. If configured as `EXPONENTIAL`, the base wait duration becomes 1.5 times larger after each failed attempt | No | +| randomizationFactor | float64 | Randomization factor for actual wait duration, a number in interval `[0, 1]`, default is 0. The actual wait duration used is a random number in interval `[(base wait duration) * (1 - randomizationFactor), (base wait duration) * (1 + randomizationFactor)]` | No | + +#### CircuitBreaker Policy + +CircuitBreaker leverges a finite state machine to implement the processing logic, the state machine has three states: `CLOSED`, `OPEN`, and `HALF_OPEN`. When the state is `CLOSED`, requests pass through normally, state transits to `OPEN` if request failure rate or slow request rate reach a configured threshold and requests will be shor-circuited in this state. After a configured duration, state transits from `OPEN` to `HALF_OPEN`, in which a limited number of requests are permitted to pass through while other requests are still short-circuited, and state transit to `CLOSED` or `OPEN` +based on the results of the permitted requests. + +When `CLOSED`, it uses a sliding window to store and aggregate the result of recent requests, the window can either be `COUNT_BASED` or `TIME_BASED`. The `COUNT_BASED` window aggregates the last N requests and the `TIME_BASED` window aggregates requests in the last N seconds, where N is the window size. + +Below is an example configuration with both `COUNT_BASED` and `TIME_BASED` policies. + +Policy `circuit-breaker-example-count` short-circuits requests if more than half of recent requests failed. + +Policy `circuit-breaker-example-time` short-circuits requests if more than 60% of recent requests failed. + +> failed means that backend filter returns non-empty results. + +```yaml +kind: CircuitBreaker +name: circuit-breaker-example-count +slidingWindowType: COUNT_BASED +failureRateThreshold: 50 +slidingWindowSize: 100 + +--- +kind: CircuitBreaker +name: circuit-breaker-example-time +slidingWindowType: TIME_BASED +failureRateThreshold: 60 +slidingWindowSize: 100 +``` + +| Name | Type | Description | Required | +|------|------|-------------|----------| +| slidingWindowType | string | Type of the sliding window which is used to record the outcome of requests when the CircuitBreaker is `CLOSED`. Sliding window can either be `COUNT_BASED` or `TIME_BASED`. If the sliding window is `COUNT_BASED`, the last `slidingWindowSize` requests are recorded and aggregated. If the sliding window is `TIME_BASED`, the requests of the last `slidingWindowSize` seconds are recorded and aggregated. Default is `COUNT_BASED` | No | +| failureRateThreshold | int8 | Failure rate threshold in percentage. When the failure rate is equal to or greater than the threshold the CircuitBreaker transitions to `OPEN` and starts short-circuiting requests. Default is 50 | No | +| slowCallRateThreshold | int8 | Slow rate threshold in percentage. The CircuitBreaker considers a request as slow when its duration is greater than `slowCallDurationThreshold`. When the percentage of slow requests is equal to or greater than the threshold, the CircuitBreaker transitions to `OPEN` and starts short-circuiting requests. Default is 100 | No | +| slowCallDurationThreshold | string | Duration threshold for slow call | No | +| slidingWindowSize | uint32 | The size of the sliding window which is used to record the outcome of requests when the CircuitBreaker is `CLOSED`. Default is 100 | No | +| permittedNumberOfCallsInHalfOpenState | uint32 | The number of permitted requests when the CircuitBreaker is `HALF_OPEN`. Default is 10 | No | +| minimumNumberOfCalls | uint32 | The minimum number of requests which are required (per sliding window period) before the CircuitBreaker can calculate the error rate or slow requests rate. For example, if `minimumNumberOfCalls` is 10, then at least 10 requests must be recorded before the failure rate can be calculated. If only 9 requests have been recorded the CircuitBreaker will not transition to `OPEN` even if all 9 requests have failed. Default is 10 | No | +| maxWaitDurationInHalfOpenState | string | The maximum wait duration which controls the longest amount of time a CircuitBreaker could stay in `HALF_OPEN` state before it switches to `OPEN`. Value 0 means CircuitBreaker would wait infinitely in `HALF_OPEN` State until all permitted requests have been completed. Default is 0| No | +| waitDurationInOpenState | string | The time that the CircuitBreaker should wait before transitioning from `OPEN` to `HALF_OPEN`. Default is 60s | No | + +See more details about `Retry`, `CircuitBreaker` or other resilience polcies in [here](../cookbook/resilience.md). diff --git a/docs/07.Reference/7.2.Filters.md b/docs/07.Reference/7.2.Filters.md new file mode 100644 index 0000000000..6ed9562ea2 --- /dev/null +++ b/docs/07.Reference/7.2.Filters.md @@ -0,0 +1,1870 @@ +# Filters + +- [Filters](#filters) + - [Proxy](#proxy) + - [Configuration](#configuration) + - [Results](#results) + - [SimpleHTTPProxy](#simplehttpproxy) + - [Configuration](#configuration-1) + - [Results](#results-1) + - [WebSocketProxy](#websocketproxy) + - [Configuration](#configuration-2) + - [Results](#results-2) + - [CORSAdaptor](#corsadaptor) + - [Configuration](#configuration-3) + - [Results](#results-3) + - [Fallback](#fallback) + - [Configuration](#configuration-4) + - [Results](#results-4) + - [Mock](#mock) + - [Configuration](#configuration-5) + - [Results](#results-5) + - [RemoteFilter](#remotefilter) + - [Configuration](#configuration-6) + - [Results](#results-6) + - [RequestAdaptor](#requestadaptor) + - [Configuration](#configuration-7) + - [Results](#results-7) + - [RequestBuilder](#requestbuilder) + - [Configuration](#configuration-8) + - [Results](#results-8) + - [RateLimiter](#ratelimiter) + - [Configuration](#configuration-9) + - [Results](#results-9) + - [ResponseAdaptor](#responseadaptor) + - [Configuration](#configuration-10) + - [Results](#results-10) + - [ResponseBuilder](#responsebuilder) + - [Configuration](#configuration-11) + - [Results](#results-11) + - [Validator](#validator) + - [Configuration](#configuration-12) + - [Results](#results-12) + - [WasmHost](#wasmhost) + - [Configuration](#configuration-13) + - [Results](#results-13) + - [Kafka](#kafka) + - [Configuration](#configuration-14) + - [Results](#results-14) + - [HeaderToJSON](#headertojson) + - [Configuration](#configuration-15) + - [Results](#results-15) + - [CertExtractor](#certextractor) + - [Configuration](#configuration-16) + - [Results](#results-16) + - [HeaderLookup](#headerlookup) + - [Configuration](#configuration-17) + - [Results](#results-17) + - [ResultBuilder](#resultbuilder) + - [Configuration](#configuration-18) + - [Results](#results-18) + - [DataBuilder](#databuilder) + - [Configuration](#configuration-19) + - [Results](#results-19) + - [OIDCAdaptor](#oidcadaptor) + - [Configuration](#configuration-20) + - [Results](#results-20) + - [OPAFilter](#opafilter) + - [Configuration](#configuration-21) + - [Results](#results-21) + - [Redirector](#redirector) + - [Configuration](#configuration-22) + - [Results](#results-22) + - [GRPCProxy](#grpcproxy) + - [Configuration](#configuration-23) + - [Results](#results-23) + - [Common Types](#common-types) + - [pathadaptor.Spec](#pathadaptorspec) + - [pathadaptor.RegexpReplace](#pathadaptorregexpreplace) + - [httpheader.AdaptSpec](#httpheaderadaptspec) + - [proxy.ServerPoolSpec](#proxyserverpoolspec) + - [proxy.Server](#proxyserver) + - [proxy.LoadBalanceSpec](#proxyloadbalancespec) + - [proxy.StickySessionSpec](#proxystickysessionspec) + - [proxy.HealthCheckSpec](#proxyhealthcheckspec) + - [proxy.MemoryCacheSpec](#proxymemorycachespec) + - [proxy.RequestMatcherSpec](#proxyrequestmatcherspec) + - [grpcproxy.ServerPoolSpec](#grpcproxyserverpoolspec) + - [grpcproxy.RequestMatcherSpec](#grpcproxyrequestmatcherspec) + - [StringMatcher](#stringmatcher) + - [proxy.MethodAndURLMatcher](#proxymethodandurlmatcher) + - [urlrule.URLRule](#urlruleurlrule) + - [proxy.Compression](#proxycompression) + - [proxy.MTLS](#proxymtls) + - [websocketproxy.WebSocketServerPoolSpec](#websocketproxywebsocketserverpoolspec) + - [mock.Rule](#mockrule) + - [mock.MatchRule](#mockmatchrule) + - [ratelimiter.Policy](#ratelimiterpolicy) + - [httpheader.ValueValidator](#httpheadervaluevalidator) + - [validator.JWTValidatorSpec](#validatorjwtvalidatorspec) + - [validator.BasicAuthValidatorSpec](#validatorbasicauthvalidatorspec) + - [basicAuth.LDAPSpec](#basicauthldapspec) + - [signer.Spec](#signerspec) + - [signer.HeaderHoisting](#signerheaderhoisting) + - [signer.Literal](#signerliteral) + - [validator.OAuth2ValidatorSpec](#validatoroauth2validatorspec) + - [validator.OAuth2TokenIntrospect](#validatoroauth2tokenintrospect) + - [validator.OAuth2JWT](#validatoroauth2jwt) + - [kafka.Topic](#kafkatopic) + - [headertojson.HeaderMap](#headertojsonheadermap) + - [headerlookup.HeaderSetterSpec](#headerlookupheadersetterspec) + - [requestadaptor.SignerSpec](#requestadaptorsignerspec) + - [Template Of Builder Filters](#template-of-builder-filters) + - [HTTP Specific](#http-specific) + +A Filter is a request/response processor. Multiple filters can be orchestrated +together to form a pipeline, each filter returns a string result after it +finishes processing the input request/response. An empty result means the +input was successfully processed by the current filter and can go forward +to the next filter in the pipeline, while a non-empty result means the pipeline +or preceding filter needs to take extra action. + +## Proxy + +The Proxy filter is a proxy of the backend service. + +Below is one of the simplest Proxy configurations, it forward requests +to `http://127.0.0.1:9095`. + +```yaml +kind: Proxy +name: proxy-example-1 +pools: +- servers: + - url: http://127.0.0.1:9095 +maxRedirection: 10 +``` + +Pool without `filter` is considered the main pool, other pools with `filter` +are considered candidate pools. Proxy first checks if one of the candidate +pools can process a request. For example, the first candidate pool in the +below configuration selects and processes requests with the header +`X-Candidate:candidate`, the second candidate pool randomly selects and +processes 400‰ of requests, and the main pool processes the other 600‰ +of requests. + +```yaml +kind: Proxy +name: proxy-example-2 +pools: +- servers: + - url: http://127.0.0.1:9095 + filter: + headers: + X-Candidate: + exact: candidate +- servers: + - url: http://127.0.0.1:9096 + filter: + permil: 400 # between 0 and 1000 + policy: random +- servers: + - url: http://127.0.0.1:9097 +maxRedirection: 10 +``` + +Servers of a pool can also be dynamically configured via service discovery, +the below configuration gets a list of servers by `serviceRegistry` & +`serviceName`, and only servers that have tag `v2` are selected. + +```yaml +kind: Proxy +name: proxy-example-3 +pools: +- serverTags: ["v2"] + serviceName: service-001 + serviceRegistry: eureka-service-registry-example +maxRedirection: 10 +``` + +When there are multiple servers in a pool, the Proxy can do a load balance +between them: + +```yaml +kind: Proxy +name: proxy-example-4 +pools: +- serverTags: ["v2"] + serviceName: service-001 + serviceRegistry: eureka-service-registry-example + loadBalance: + policy: roundRobin +maxRedirection: 10 +``` + +### Configuration +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| pools | [proxy.ServerPoolSpec](#proxyserverpoolspec) | The pool without `filter` is considered the main pool, other pools with `filter` are considered candidate pools, and a `Proxy` must contain exactly one main pool. When `Proxy` gets a request, it first goes through the candidate pools, and if one of the pool's filter matches the request, servers of this pool handle the request, otherwise, the request is passed to the main pool. | Yes | +| mirrorPool | [proxy.ServerPoolSpec](#proxyserverpoolspec) | Define a mirror pool, requests are sent to this pool simultaneously when they are sent to candidate pools or main pool | No | +| compression | [proxy.Compression](#proxyCompression) | Response compression options | No | +| mtls | [proxy.MTLS](#proxymtls) | mTLS configuration | No | +| maxIdleConns | int | Controls the maximum number of idle (keep-alive) connections across all hosts. Default is 10240 | No | +| maxIdleConnsPerHost | int | Controls the maximum idle (keep-alive) connections to keep per-host. Default is 1024 | No | +| serverMaxBodySize | int64 | Max size of response body. the default value is 4MB. Responses with a body larger than this option are discarded. When this option is set to `-1`, Easegress takes the response body as a stream and the body can be any size, but some features are not possible in this case, please refer [Stream](./stream.md) for more information. | No | +| maxRedirection | int | The maxRedirection parameter determines the maximum number of redirections allowed by the HTTP client for each request. A default value of zero means that redirection is not allowed, while a number greater than zero specifies the maximum allowed number of redirections. | No | + +### Results + +| Value | Description | +| ------------- | -------------------------------------------------------| +| internalError | Encounters an internal error | +| clientError | Client-side (Easegress) network error | +| serverError | Server-side network error | +| failureCode | Resp failure code matches failureCodes set in poolSpec | + +## SimpleHTTPProxy + +The `SimpleHTTPProxy` filter is a simplified version of the Proxy filter, unlike +`Proxy`, which are mainly used as reverse proxy, this filter is mainly for +forward proxies. + +The following example demonstrates a basic configuration for `SimpleHTTPProxy`. +Unlike the `Proxy` filter, the backend service's address is not specified in +the `SimpleHTTPProxy` configuration. Instead, the request URL is used directly, +allowing for the use of a single `SimpleHTTPProxy` instance for any backend +services. + +```yaml +name: simple-http-proxy +kind: Pipeline +flow: + - filter: requestBuilder + - filter: proxy + +filters: + - kind: RequestBuilder + name: requestBuilder + template: | + url: http://127.0.0.1:9095 + method: GET + + - kind: SimpleHTTPProxy + name: proxy +``` + +### Configuration +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| retryPolicy | string | Retry policy name | No | +| timeout | string | Request calceled when timeout | No | +| compression | [proxy.Compression](#proxyCompression) | Response compression options | No | +| maxIdleConns | int | Controls the maximum number of idle (keep-alive) connections across all hosts. Default is 10240 | No | +| maxIdleConnsPerHost | int | Controls the maximum idle (keep-alive) connections to keep per-host. Default is 1024 | No | +| serverMaxBodySize | int64 | Max size of response body. the default value is 4MB. Responses with a body larger than this option are discarded. When this option is set to `-1`, Easegress takes the response body as a stream and the body can be any size, but some features are not possible in this case, please refer [Stream](./stream.md) for more information. | No | + +### Results + +| Value | Description | +| ------------- | -------------------------------------------------------| +| internalError | Encounters an internal error | +| clientError | Client-side (Easegress) network error | +| serverError | Server-side network error | + +## WebSocketProxy + +The WebSocketProxy filter is a proxy of the websocket backend service. + +Below is one of the simplest WebSocketProxy configurations, it forwards +the websocket connection to `ws://127.0.0.1:9095`. + +```yaml +kind: WebSocketProxy +name: proxy-example-1 +pools: +- servers: + - url: ws://127.0.0.1:9095 +``` + +Same as the `Proxy` filter: +* a `filter` can be configured on a pool. +* the servers of a pool can be dynamically configured via service discovery. +* When there are multiple servers in a pool, the pool can do a load balance + between them. + +Note, when routing traffic to a pipeline with a `WebSocketProxy`, the +`HTTPServer` must set the corresponding `clientMaxBodySize` to `-1`, as +below: + +```yaml +name: demo-server +kind: HTTPServer +port: 8080 +rules: +- paths: + path: /ws + clientMaxBodySize: -1 # REQUIRED! + backend: websocket-pipeline +``` + +### Configuration +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| pools | [websocketproxy.WebSocketServerPoolSpec](#websocketproxywebsocketserverpoolspec) | The pool without `filter` is considered the main pool, other pools with `filter` are considered candidate pools, and a `Proxy` must contain exactly one main pool. When `WebSocketProxy` gets a request, it first goes through the candidate pools, and if it matches one of the pool's filter, servers of this pool handle the connection, otherwise, it is passed to the main pool. | Yes | + +### Results + +| Value | Description | +| ------------- | -------------------------------------------------------| +| internalError | Encounters an internal error | +| clientError | Client-side network error | + +## CORSAdaptor + +The CORSAdaptor handles the [CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) preflight, simple and not so simple request for the backend service. + +The below example configuration handles the CORS `GET` request from `*.megaease.com`. + +```yaml +kind: CORSAdaptor +name: cors-adaptor-example +allowedOrigins: ["http://*.megaease.com"] +allowedMethods: [GET] +``` + +### Configuration +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| allowedOrigins | []string | An array of origins a cross-domain request can be executed from. If the special `*` value is present in the list, all origins will be allowed. An origin may contain a wildcard (*) to replace 0 or more characters (i.e.: http://*.domain.com). Usage of wildcards implies a small performance penalty. Only one wildcard can be used per origin. Default value is `*` | No | +| allowedMethods | []string | An array of methods the client is allowed to use with cross-domain requests. The default value is simple methods (HEAD, GET, and POST) | No | +| allowedHeaders | []string | An array of non-simple headers the client is allowed to use with cross-domain requests. If the special `*` value is present in the list, all headers will be allowed. The default value is [] but "Origin" is always appended to the list | No | +| allowCredentials | bool | Indicates whether the request can include user credentials like cookies, HTTP authentication, or client-side SSL certificates | No | +| exposedHeaders | []string | Indicates which headers are safe to expose to the API of a CORS API specification | No | +| maxAge | int | Indicates how long (in seconds) the results of a preflight request can be cached. The default is 0 stands for no max age | No | + +### Results + +| Value | Description | +| ----------- | ------------------------------------------------------------------- | +| preflighted | The request is a preflight one and has been processed successfully. | +| rejected | The request was rejected by CORS checking. | + +## Fallback + +The Fallback filter mocks a response as the fallback action of other filters. +The below example configuration mocks the response with a specified status +code, headers, and body. + +```yaml +kind: Fallback +name: fallback-example +mockCode: 200 +mockHeaders: + Content-Type: application/json +mockBody: '{"message": "The feature turned off, please try it later."}' +``` + +### Configuration + +| Name | Type | Description | Required | +| ----------- | ----------------- | ------------------------------------------------------------------------------------ | -------- | +| mockCode | int | This code overwrites the status code of the original response | Yes | +| mockHeaders | map[string]string | Headers to be added/set to the original response | No | +| mockBody | string | Default is an empty string, overwrite the body of the original response if specified | No | + +### Results + +| Value | Description | +| -------- | ---------------------------------------------------------------------------- | +| fallback | The fallback steps have been executed, this filter always return this result | +| responseNotFound | No response found | + +## Mock + +The Mock filter mocks responses according to configured rules, mainly for +testing purposes. + +Below is an example configuration to mock response for requests to path +`/users/1` with specified status code, headers, and body, also with a 100ms +delay to mock the time for request processing. + +```yaml +kind: Mock +name: mock-example +rules: +- match: + path: /users/1 + code: 200 + headers: + Content-Type: application/json + body: '{"name": "alice", "age": 30}' + delay: 100ms +``` + +### Configuration + +| Name | Type | Description | Required | +| ----- | ------------------------ | ------------- | -------- | +| rules | [][mock.Rule](#mockRule) | Mocking rules | Yes | + +### Results + +| Value | Description | +| ------ | ----------------------------------------------------------------- | +| mocked | The request matches one of the rules and response has been mocked | + +## RemoteFilter + +The RemoteFilter is a filter making remote service act as an internal filter. +It forwards original request & response information to the remote service and +returns a result according to the response of the remote service. + +The below example configuration forwards request & response information to +`http://127.0.0.1:9096/verify`. + +```yaml +kind: RemoteFilter +name: remote-filter-example +url: http://127.0.0.1:9096/verify +timeout: 500ms +``` + +### Configuration + +| Name | Type | Description | Required | +| ------- | ------ | -------------------------------------- | -------- | +| url | string | Address of remote service | Yes | +| timeout | string | Timeout duration of the remote service | No | + +### Results + +| Value | Description | +| --------------- | --------------------------------------------------------------------------------------------- | +| failed | Failed to send the request to remote service, or remote service returns a non-2xx status code | +| responseAlready | The remote service returns status code 205 | + +## RequestAdaptor + +The RequestAdaptor modifies the original request according to configuration. + +The example configuration below adds prefix `/v3` to the request path. + +```yaml +kind: RequestAdaptor +name: request-adaptor-example +path: + addPrefix: /v3 +``` + +The example configuration below removes header `X-Version` from all `GET` requests. + +```yaml +kind: RequestAdaptor +name: request-adaptor-example +method: GET +header: + del: ["X-Version"] +``` + +The example configuration below modifies the request path using regular expressions. + +```yaml +kind: RequestAdaptor +name: request-adaptor-example +path: + regexpReplace: + regexp: "^/([a-z]+)/([a-z]+)" # groups /$1/$2 for lowercase alphabet + replace: "/$2/$1" # changes the order of groups +``` + +The example configuration below signs the request using the +[Amazon Signature V4](https://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html) +signing process, with the default configuration of this signing process. + +```yaml +kind: RequestAdaptor +name: request-adaptor-example +path: + signer: + for: "aws4" +``` + +### Configuration + +| Name | Type | Description | Required | +| -----------| -------------------------------------------- |------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -------- | +| method | string | If provided, the method of the original request is replaced by the value of this option | No | +| path | [pathadaptor.Spec](#pathadaptorSpec) | Rules to revise request path | No | +| header | [httpheader.AdaptSpec](#httpheaderAdaptSpec) | Rules to revise request header | No | +| body | string | If provided the body of the original request is replaced by the value of this option. | No | +| host | string | If provided the host of the original request is replaced by the value of this option. | No | +| decompress | string | If provided, the request body is replaced by the value of decompressed body. Now support "gzip" decompress | No | +| compress | string | If provided, the request body is replaced by the value of compressed body. Now support "gzip" compress | No | +| sign | [requestadaptor.SignerSpec](#requestadaptorsignerspec) | If provided, sign the request using the [Amazon Signature V4](https://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html) signing process with the configuration | No | +| template | string | template to create request adaptor, please refer the [template](#template-of-builder-filters) for more information | No | +| leftDelim | string | left action delimiter of the template, default is `{{` | No | +| rightDelim | string | right action delimiter of the template, default is `}}` | No | + +**NOTE**: template field takes higher priority than the static field with the same name. + +### Results + +| Value | Description | +| -------------- | ---------------------------------------- | +| decompressFail | the request body can not be decompressed | +| compressFail | the request body can not be compressed | +| signFail | the request body can not be signed | + +## RequestBuilder + +The RequestBuilder creates a new request from existing requests/responses +according to the configuration, and saves the new request into [the +namespace it is bound](controllers.md#pipeline). + +The example configuration below creates a reference to the request of +namespace `DEFAULT`. + +```yaml +name: requestbuilder-example-1 +kind: RequestBuilder +protocol: http +sourceNamespace: DEFAULT +``` + +The example configuration below creates an HTTP request with method `GET`, +url `http://127.0.0.1:8080`, header `X-Mock-Header:mock-value`, and body +`this is the body`. + +```yaml +name: requestbuilder-example-1 +kind: RequestBuilder +protocol: http +template: | + method: get + url: http://127.0.0.1:8080 + headers: + X-Mock-Header: + - mock-value + body: "this is the body" +``` + +### Configuration + +| Name | Type | Description | Required | +|-----------------|--------|-----------------------------------------------|----------| +| protocol | string | protocol of the request to build, default is `http`. | No | +| sourceNamespace | string | add a reference to the request of the source namespace | No | +| template | string | template to create request, the schema of this option must conform with `protocol`, please refer the [template](#template-of-builder-filters) for more information | No | +| leftDelim | string | left action delimiter of the template, default is `{{` | No | +| rightDelim | string | right action delimiter of the template, default is `}}` | No | + +**NOTE**: `sourceNamespace` and `template` are mutually exclusive, you must +set one and only one of them. + +### Results + +| Value | Description | +| -------------- | ---------------------------------------- | +| buildErr | error happens when build request | + +## RateLimiter + +RateLimiter protects backend service for high availability and reliability +by limiting the number of requests sent to the service in a configured duration. + +Below example configuration limits `GET`, `POST`, `PUT`, `DELETE` requests +to path which matches regular expression `^/pets/\d+$` to 50 per 10ms, and a +request fails if it cannot be permitted in 100ms due to high concurrency +requests count. + +```yaml +kind: RateLimiter +name: rate-limiter-example +policies: +- name: policy-example + timeoutDuration: 100ms + limitRefreshPeriod: 10ms + limitForPeriod: 50 +defaultPolicyRef: policy-example +urls: +- methods: [GET, POST, PUT, DELETE] + url: + regex: ^/pets/\d+$ + policyRef: policy-example +``` + +### Configuration + +| Name | Type | Description | Required | +| ---------------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------- | +| policies | [][urlrule.URLRule](#urlruleURLRule) | Policy definitions | Yes | +| defaultPolicyRef | string | The default policy, if no `policyRef` is configured in one of the `urls`, it uses this policy | No | +| urls | [][resilience.URLRule](#resilienceURLRule) | An array of request match criteria and policy to apply on matched requests. Note that a standalone RateLimiter instance is created for each item of the array, even two or more items can refer to the same policy | Yes | + +### Results + +| Value | Description | +| ----------- | ---------------------------------------------------------- | +| rateLimited | The request has been rejected as a result of rate limiting | + + +## ResponseAdaptor + +The ResponseAdaptor modifies the input response according to the configuration. + +Below is an example configuration that adds a header named `X-Response-Adaptor` +with the value `response-adaptor-example` to the input response. + +```yaml +kind: ResponseAdaptor +name: response-adaptor-example +header: + add: + X-Response-Adaptor: response-adaptor-example +``` + +### Configuration + +| Name | Type | Description | Required | +| ------ | -------- |---------------------------------------------------------------------------------------------------------------------| -------- | +| header | [httpheader.AdaptSpec](#httpheaderAdaptSpec) | Rules to revise request header | No | +| body | string | If provided the body of the original request is replaced by the value of this option. | No | +| compress | string | compress body, currently only support gzip | No | +| decompress | string | decompress body, currently only support gzip | No | +| template | string | template to create response adaptor, please refer the [template](#template-of-builder-filters) for more information | No | +| leftDelim | string | left action delimiter of the template, default is `{{` | No | +| rightDelim | string | right action delimiter of the template, default is `}}` | No | + +**NOTE**: template field takes higher priority than the static field with the same name. + +### Results + +| Value | Description | +| ---------------- | ---------------------------------------------------------- | +| responseNotFound | responseNotFound response is not found | +| decompressFailed | error happens when decompress body | +| compressFailed | error happens when compress body | + +## ResponseBuilder + +The ResponseBuilder creates a new response from existing requests/responses +according to the configuration, and saves the new response into [the +namespace it is bound](controllers.md#pipeline). + +The example configuration below creates a reference to the response of +namespace `DEFAULT`. + +```yaml +name: responsebuilder-example-1 +kind: ResponseBuilder +protocol: http +sourceNamespace: DEFAULT +``` + +The example configuration below creates an HTTP response with status code +200, header `X-Mock-Header:mock-value`, and body `this is the body`. + +```yaml +name: responsebuilder-example-1 +kind: ResponseBuilder +protocol: http +template: | + statusCode: 200 + headers: + X-Mock-Header: + - mock-value + body: "this is the body" +``` + +### Configuration + +| Name | Type | Description | Required | +|-----------------|--------|-----------------------------------------------|----------| +| protocol | string | protocol of the response to build, default is `http`. | No | +| sourceNamespace | string | add a reference to the response of the source namespace | No | +| template | string | template to create response, the schema of this option must conform with `protocol`, please refer the [template](#template-of-builder-filters) for more information | No | +| leftDelim | string | left action delimiter of the template, default is `{{` | No | +| rightDelim | string | right action delimiter of the template, default is `}}` | No | + +**NOTE**: `sourceNamespace` and `template` are mutually exclusive, you must +set one and only one of them. + +### Results +| Value | Description | +| -------------- | ---------------------------------------- | +| buildErr | error happens when build response. | + +## Validator + +The Validator filter validates requests, forwards valid ones, and rejects +invalid ones. Four validation methods (`headers`, `jwt`, `signature`, `oauth2` +and `basicAuth`) are supported up to now, and these methods can either be +used together or alone. When two or more methods are used together, a request +needs to pass all of them to be forwarded. + +Below is an example configuration for the `headers` validation method. +Requests which has a header named `Is-Valid` with value `abc` or `goodplan` +or matches regular expression `^ok-.+$` are considered to be valid. + +```yaml +kind: Validator +name: header-validator-example +headers: + Is-Valid: + values: ["abc", "goodplan"] + regexp: "^ok-.+$" +``` + +Below is an example configuration for the `jwt` validation method. + +```yaml +kind: Validator +name: jwt-validator-example +jwt: + cookieName: auth + algorithm: HS256 + secret: 6d79736563726574 +``` + +Below is an example configuration for the `signature` validation method, +note multiple access keys id/secret pairs can be listed in `accessKeys`, +but there's only one pair here as an example. + +```yaml +kind: Validator +name: signature-validator-example +signature: + accessKeys: + AKID: SECRET +``` + +Below is an example configuration for the `oauth2` validation method which +uses a token introspection server for validation. + +```yaml +kind: Validator +name: oauth2-validator-example +oauth2: + tokenIntrospect: + endPoint: https://127.0.0.1:8443/auth/realms/test/protocol/openid-connect/token/introspect + clientId: easegress + clientSecret: 42620d18-871d-465f-912a-ebcef17ecb82 + insecureTls: false +``` + +Here's an example of `basicAuth` validation method which uses +[Apache2 htpasswd](https://manpages.debian.org/testing/apache2-utils/htpasswd.1.en.html) +formatted encrypted password file for validation. + +```yaml +kind: Validator +name: basicAuth-validator-example +basicAuth: + mode: "FILE" + userFile: /etc/apache2/.htpasswd +``` + +### Configuration + +| Name | Type | Description | Required | +| --------- | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | +| headers | map[string][httpheader.ValueValidator](#httpheaderValueValidator) | Header validation rules, the key is the header name and the value is validation rule for corresponding header value, a request needs to pass all of the validation rules to pass the `headers` validation | No | +| jwt | [validator.JWTValidatorSpec](#validatorJWTValidatorSpec) | JWT validation rule, validates JWT token string from the `Authorization` header or cookies | No | +| signature | [signer.Spec](#signerSpec) | Signature validation rule, implements an [Amazon Signature V4](https://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html) compatible signature validation validator, with customizable literal strings | No | +| oauth2 | [validator.OAuth2ValidatorSpec](#validatorOAuth2ValidatorSpec) | The `OAuth/2` method support `Token Introspection` mode and `Self-Encoded Access Tokens` mode, only one mode can be configured at a time | No | +| basicAuth | [validator.BasicAuthValidatorSpec](#validatorBasicAuthValidatorSpec) | The `BasicAuth` method support `FILE`, `ETCD` and `LDAP` mode, only one mode can be configured at a time. | No | + +### Results + +| Value | Description | +| ------- | ----------------------------------- | +| invalid | The request doesn't pass validation | + +## WasmHost + +The WasmHost filter implements a host environment for user-developed +[WebAssembly](https://webassembly.org/) code. Below is an example +configuration that loads wasm code from a file, and more details could be +found in [this document](./wasmhost.md). + +```yaml +name: wasm-host-example +kind: WasmHost +maxConcurrency: 2 +code: /home/megaease/wasm/hello.wasm +timeout: 200ms +``` + +Note: this filter is disabled in the default build of `Easegress`, it can +be enabled by: + +```bash +$ make GOTAGS=wasmhost +``` + +or + +```bash +$ make wasm +``` + + + +### Configuration + +| Name | Type | Description | Required | +| -------------- | ----------------- | ----------------------------------------------------------------------------------------------- | -------- | +| maxConcurrency | int32 | The maximum requests the filter can process concurrently. Default is 10 and minimum value is 1. | Yes | +| code | string | The wasm code, can be the base64 encoded code, or path/url of the file which contains the code. | Yes | +| timeout | string | Timeout for wasm execution, default is 100ms. | Yes | +| parameters | map[string]string | Parameters to initialize the wasm code. | No | + + +### Results + +| Value | Description | +| --------------------------------------------------------------------------- | -------------------------------------------------- | +| outOfVM | Can not found an available wasm VM. | +| wasmError | An error occurs during the execution of wasm code. | +| wasmResult1 Results defined and returned by wasm code. | +| ... | +| wasmResult9 | + + + +## Kafka + +The Kafka filter converts HTTP Requests to Kafka messages and sends them to +the Kafka backend. The topic of the Kafka message comes from the HTTP header, +if not found, then the default topic will be used. The payload of the Kafka +message comes from the body of the HTTP Request. + +Below is an example configuration. + +```yaml +kind: Kafka +name: kafka-example +backend: [":9093"] +topic: + default: kafka-topic + dynamic: + header: X-Kafka-Topic +``` + +### Configuration + +| Name | Type | Description | Required | +| ------------ | -------- | -------------------------------- | -------- | +| backend | []string | Addresses of Kafka backend | Yes | +| topic | [Kafka.Topic](#kafkatopic) | the topic is Spec used to get Kafka topic used to send message to the backend | Yes | + + +### Results + +| Value | Description | +| ----------------------- | ------------------------------------ | +| parseErr | Failed to get Kafka message from the HTTP request | + +## HeaderToJSON + +The HeaderToJSON converts HTTP headers to JSON and combines it with the HTTP +request body. To use this filter, make sure your HTTP Request body is empty +or JSON schema. + +Below is an example configuration. + +```yaml +kind: HeaderToJSON +name: headertojson-example +headerMap: + - header: X-User-Name + json: username + - header: X-Type + json: type +``` + +### Configuration + +| Name | Type | Description | Required | +| ------------ | -------- | -------------------------------- | -------- | +| headerMap | [][HeaderToJSON.HeaderMap](#headertojsonheadermap) | headerMap defines a map between HTTP header name and corresponding JSON field name | Yes | + + +### Results + +| Value | Description | +| ----------------------- | --------------------------------------- | +| jsonEncodeDecodeErr | Failed to convert HTTP headers to JSON. | +| bodyReadErr | Request body is stream | + +## CertExtractor + +CertExtractor extracts a value from requests TLS certificates Subject or +Issuer metadata (https://pkg.go.dev/crypto/x509/pkix#Name) and adds the +value to headers. Request can contain zero or multiple certificates so +the position (first, second, last, etc) of the certificate in the chain +is required. + +Here's an example configuration, that adds a new header `tls-cert-postalcode`, +based on the PostalCode of the last TLS certificate's Subject: + +```yaml +kind: "CertExtractor" +name: "postalcode-extractor" +certIndex: -1 # take last certificate in chain +target: "subject" +field: "PostalCode" +headerKey: "tls-cert-postalcode" +``` + +### Configuration + +| Name | Type | Description | Required | +| ------------ | -------- | -------------------------------- | -------- | +| certIndex | int16 | The index of the certificate in the chain. Negative indexes from the end of the chain (-1 is the last index, -2 second last etc.) | Yes | +| target | string | Either `subject` or `issuer` of the [x509.Certificate](https://pkg.go.dev/crypto/x509#Certificate) | Yes | +| field | string | One of the string or string slice fields from https://pkg.go.dev/crypto/x509/pkix#Name | Yes | +| headerKey | string | Extracted value is added to this request header key. | Yes | + +### Results +The CertExtractor is always success and returns no results. + +## HeaderLookup + +HeaderLookup checks [custom data](customdata.md) stored in etcd and put them into HTTP header. + +Suppose you create a custom data kind of `client-info` and post a data key `client1` with the value: +```yaml +name: client1 +id: 123 +kind: vip +``` + +Then HeaderLookup with the following configuration adds `X-Id:123` and `X-Kind:vip` to HTTP request header. +```yaml +name: headerlookup-example-1 +kind: HeaderLookup +etcdPrefix: client-info # get custom data kind +headerKey: client1 # get custom data name +headerSetters: +- etcdKey: id # custom data value of id + headerKey: X-Id +- etcdKey: kind # custom data value of kind + headerKey: X-Kind +``` + +You can also use `pathRegExp` to check different keys for different requests. When `pathRegExp` is defined, `pathRegExp` is used with `regexp.FindStringSubmatch` to identify a group from the path. The first captured group is appended to the etcd key in the following format: `{headerKey's value}-{regex group}`. + +Suppose you create a custom data kind of `client-info` and post several data: +```yaml +name: client-abc +id: 123 +kind: vip + +name: client-def +id: 124 +kind: vvip +``` + +Then HeaderLookup with the following configuration adds `X-Id:123` and `X-Kind:vip` for requests with path `/api/abc`, adds `X-Id:124` and `X-Kind:vvip` for requests with path `/api/def`. +```yaml +name: headerlookup-example-1 +kind: HeaderLookup +etcdPrefix: client-info # get custom data kind +headerKey: client # get custom data name +pathRegExp: "^/api/([a-z]+)" +headerSetters: +- etcdKey: id # custom data value of id + headerKey: X-Id +- etcdKey: kind # custom data value of kind + headerKey: X-Kind +``` + +### Configuration +| Name | Type | Description | Required | +|------|------|-------------|----------| +| etcdPrefix | string | Kind of custom data | Yes | +| headerKey | string | Name of custom data in given kind | Yes | +| pathRegExp | string | Reg used to get key from request path | No | +| headerSetters | [][headerlookup.HeaderSetterSpec](#headerlookup.HeaderSetterSpec) | Set custom data value to http header | Yes | +### Results + +HeaderLookup has no results. + +## ResultBuilder + +ResultBuilder generates a string, which will be the result of the filter. This +filter exists to work with the [`jumpIf` mechanism](./controllers.md#pipeline) +for conditional jumping. + +Currently, the result string can only be `result0` - `result9`, this will be +changed in the future to allow arbitrary result string. + +For example, we can use the following configuration to check if the request +body contains an `image` field, and forward it to `proxy1` or `proxy2` +conditionally. + +```yaml +name: demo-pipeline +kind: Pipeline +flow: +- filter: resultBuilder + jumpIf: + result1: proxy1 + result2: proxy2 +- filter: proxy1 +- filter: END +- filter: proxy2 + +filters: +- name: resultBuilder + kind: ResultBuilder + template: | + {{- if .requests.DEFAULT.JSONBody.image}}result1{{else}}result2{{end -}} +``` + +### Configuration + +| Name | Type | Description | Required | +|-----------------|--------|-----------------------------------------------|----------| +| template | string | template to create result, please refer the [template](#template-of-builer-filters) for more information | No | +| leftDelim | string | left action delimiter of the template, default is `{{` | No | +| rightDelim | string | right action delimiter of the template, default is `}}` | No | + + +### Results + +| Value | Description | +| --------------------------------------------------------------------------- | -------------------------------------------------- | +| unknown | The ResultBuilder generates an unknown result. | +| buildErr | Error happens when build the result. | +| result0 Results defined and returned by the template . | +| ... | +| result9 | + +## DataBuilder + +DataBuilder is used to manipulate and store data. The data from the previous +filter can be transformed and stored in the context so that the data can be +used in subsequent filters. + +The example below shows how to use DataBuilder to store the request body in +the context. + +```yaml +- name: requestBodyDataBuilder + kind: DataBuilder + dataKey: requestBody + template: | + {{.requests.DEFAULT.JSONBody | jsonEscape}} +``` + +### Configuration + +| Name | Type | Description | Required | +|-----------------|--------|-----------------------------------------------|----------| +| template | string | template to create data, please refer the [template](#template-of-builer-filters) for more information | Yes | +| dataKey | string | key to store data | Yes | +| leftDelim | string | left action delimiter of the template, default is `{{` | No | +| rightDelim | string | right action delimiter of the template, default is `}}` | No | + + +### Results + +| Value | Description | +|-----------------|---------------------------------------------------| +| buildErr | Error happens when building the data | + +## OIDCAdaptor + +OpenID Connect(OIDC) is an identity layer on top of the OAuth 2.0 protocol. It enables Clients to verify +the identity of the End-User based on the authentication performed by an Authorization Server, as well as +to obtain basic profile information about the End-User. + +For identity platforms that implement standard OIDC specification like [Google Accounts](https://accounts.google.com)、[OKTA](https://www.okta.com/)、 +[Auth0](https://auth0.com/)、[Authing](https://www.authing.cn/). configure `discovery` endpoint as below example: + +```yaml +name: demo-pipeline +kind: Pipeline +flow: + - filter: oidc + jumpIf: { oidcFiltered: END } +filters: + - name: oidc + kind: OIDCAdaptor + cookieName: oidc-auth-cookie + clientId: + clientSecret: + discovery: https://accounts.google.com/.well-known/openid-configuration #Replace your own discovery + redirectURI: /oidc/callback +``` + +For third platforms that only implement OAuth2.0 like GitHub, users should configure `authorizationEndpoint`、 +`tokenEndpoint`、`userinfoEndpoint` at the same time as below example: + +```yaml +name: demo-pipeline +kind: Pipeline +flow: + - filter: oidc + jumpIf: { oidcFiltered: END } +filters: + - name: oidc + kind: OIDCAdaptor + cookieName: oidc-auth-cookie + clientId: + clientSecret: + authorizationEndpoint: https://github.com/login/oauth/authorize + tokenEndpoint: https://github.com/login/oauth/access_token + userinfoEndpoint: https://api.github.com/user + redirectURI: /oidc/callback +``` + +### Configuration + +| Name | Type | Description | Required | +|-----------------------|--------|---------------------------------------------------------------------------------------------------------------------------|----------| +| clientId | string | The OAuth2.0 app client id | Yes | +| clientSecret | string | The OAuth2.0 app client secret | Yes | +| cookieName | string | Used to check if necessary to launch OpenIDConnect flow | No | +| discovery | string | Standard OpenID Connect discovery endpoint URL of the identity server | No | +| authorizationEndpoint | string | OAuth2.0 authorization endpoint URL | No | +| tokenEndpoint | string | OAuth2.0 token endpoint URL | No | +| userInfoEndpoint | string | OAuth2.0 user info endpoint URL | No | +| redirectURI | string | The callback uri registered in identity server, for example:
`https://example.com/oidc/callback` or `/oidc/callback` | Yes | + +### Results +| Value | Description | +|-----------------|----------------------------------------| +| oidcFiltered | The request is handled by OIDCAdaptor. | + + +After OIDCAdaptor handled, following OIDC related information can be obtained from Easegress HTTP request headers: + +* **X-User-Info**: Base64 encoded OIDC End-User basic profile. +* **X-Origin-Request-URL**: End-User origin request URL before OpenID Connect or OAuth2.0 flow. +* **X-Id-Token**: The ID Token returned by OpenID Connect flow. +* **X-Access-Token**: The AccessToken returned by OpenId Connect or OAuth2.0 flow. + + + +## OPAFilter +The [Open Policy Agent (OPA)](https://www.openpolicyagent.org/docs/latest/) is an open source, +general-purpose policy engine that unifies policy enforcement across the stack. It provides a +high-level declarative language, which can be used to define and enforce policies in +Easegress API Gateway. Currently, there are 160+ built-in operators and functions we can use, +for examples `net.cidr_contains` and `contains`. + +```yaml +name: demo-pipeline +kind: Pipeline +flow: + - filter: opa-filter + jumpIf: { opaDenied: END } +filters: + - name: opa-filter + kind: OPAFilter + defaultStatus: 403 + readBody: true + includedHeaders: a,b,c + policy: | + package http + default allow = false + allow { + input.request.method == "POST" + input.request.scheme == "https" + contains(input.request.path, "/") + net.cidr_contains("127.0.0.0/24",input.request.realIP) + } +``` + +The following table lists input request fields that can be used in an OPA policy to help enforce it. + +| Name | Type | Description | Example | +|--------------------------|--------|-----------------------------------------------------------------------|--------------------------------------| +| input.request.method | string | The current http request method | "POST" | +| input.request.path | string | The current http request URL path | "/a/b/c" | +| input.request.path_parts | array | The current http request URL path parts | ["a","b","c"] | +| input.request.raw_query | string | The current http request raw query | "a=1&b=2&c=3" | +| input.request.query | map | The current http request query map | {"a":1,"b":2,"c":3} | +| input.request.headers | map | The current http request header map targeted by
includedHeaders | {"Content-Type":"application/json"} | +| input.request.scheme | string | The current http request scheme | "https" | +| input.request.realIP | string | The current http request client real IP | "127.0.0.1" | +| input.request.body | string | The current http request body string data | {"data":"xxx"} | + + +### Configuration + +| Name | Type | Description | Required | +|------------------|--------|--------------------------------------------------------------------------------------|----------| +| defaultStatus | int | The default HTTP status code when request is denied by the OPA policy decision | No | +| readBody | bool | Whether to read request body as OPA policy data on condition | No | +| includedHeaders | string | Names of the HTTP headers to be included in `input.request.headers`, comma-separated | No | +| policy | string | The OPA policy written in the Rego declarative language | Yes | + +### Results +| Value | Description | +|-----------|-----------------------------------------------| +| opaDenied | The request is denied by OPA policy decision. | + + +## Redirector + +The `Redirector` filter is used to do HTTP redirect. `Redirector` matches request url, do replacement, and return response with status code of `3xx` and put new path in response header with key of `Location`. + +Here a simple example: +```yaml +name: demo-pipeline +kind: Pipeline +flow: +- filter: redirector +filters: +- name: redirector + kind: Redirector + match: "^/users/([0-9]+)" + replacement: "http://example.com/display?user=$1" +``` +In this example, request with path `/users/123` will redirect to `http://example.com/display?user=123`. +``` +HTTP/1.1 301 Moved Permanently +Location: http://example.com/display?user=123 +``` + +More details about spec: + +We use [ReplaceAllString](https://pkg.go.dev/regexp#Regexp.ReplaceAllString) to do match and replace and put output into response header with key `Location`. By default, we use `URI` as input, but you can change input by control parameter of `matchPart`. + +```yaml +name: demo-pipeline +kind: Pipeline +flow: +- filter: redirector +filters: +- name: redirector + kind: Redirector + match: "^/users/([0-9]+)" + # by default, value of matchPart is uri, supported values: uri, path, full. + matchPart: "full" + replacement: "http://example.com/display?user=$1" +``` + +For request with URL of `https://example.com:8080/apis/v1/user?id=1`, URI part is `/apis/v1/user?id=1`, path part is `/apis/v1/user` and full part is `https://example.com:8080/apis/v1/user?id=1`. + +By default, we return status code of `301` "Moved Permanently". To return status code of `302` "Found" or other `3xx`, change `statusCode` in yaml. + +```yaml +name: demo-pipeline +kind: Pipeline +flow: +- filter: redirector +filters: +- name: redirector + kind: Redirector + match: "^/users/([0-9]+)" + # default value of 301, supported values: 301, 302, 303, 304, 307, 308. + statusCode: 302 + replacement: "http://example.com/display?user=$1" +``` + +Following are some common used examples: +1. URI prefix redirect +```yaml +name: demo-pipeline +kind: Pipeline +flow: +- filter: redirector +filters: +- name: redirector + kind: Redirector + match: "^(.*)$" + matchPart: "uri" + replacement: "/prefix$1" +``` + +``` +input: https://example.com/path/to/api/?key1=123&key2=456 +output: /prefix/path/to/api/?key1=123&key2=456 +``` + +URI prefix redirect with schema and host: +```yaml +name: demo-pipeline +kind: Pipeline +flow: +- filter: redirector +filters: +- name: redirector + kind: Redirector + match: "(^.*\/\/)([^\/]*)(.*)$" + matchPart: "full" + replacement: "${1}${2}/prefix$3" +``` +``` +input: https://example.com/path/to/api/?key1=123&key2=456 +output: https://example.com/prefix/path/to/api/?key1=123&key2=456 +``` + +2. Domain Redirect +```yaml +name: demo-pipeline +kind: Pipeline +flow: +- filter: redirector +filters: +- name: redirector + kind: Redirector + match: "(^.*\/\/)([^\/]*)(.*$)" + matchPart: "full" + # use ${1} instead of $1 here. + replacement: "${1}my.com${3}" +``` +``` +input: https://example.com/path/to/api/?key1=123&key2=456 +output: https://my.com/path/to/api/?key1=123&key2=456 +``` + +3. Path Redirect +```yaml +name: demo-pipeline +kind: Pipeline +flow: +- filter: redirector +filters: +- name: redirector + kind: Redirector + match: "/path/to/(user)\.php\?id=(\d*)" + matchPart: "uri" + replacement: "/api/$1/$2" +``` +``` +input: https://example.com/path/to/user.php?id=123 +output: /api/user/123 +``` + +Path redirect with schema and host: +```yaml +name: demo-pipeline +kind: Pipeline +flow: +- filter: redirector +filters: +- name: redirector + kind: Redirector + match: "(^.*\/\/)([^\/]*)/path/to/(user)\.php\?id=(\d*)" + matchPart: "full" + replacement: "${1}${2}/api/$3/$4" +``` +``` +input: https://example.com/path/to/user.php?id=123 +output: https://example.com/api/user/123 +``` + + +### Configuration +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| match | string | Regular expression to match request path. The syntax of the regular expression is [RE2](https://golang.org/s/re2syntax) | Yes | +| matchPart | string | Parameter to decide which part of url used to do match, supported values: uri, full, path. Default value is uri. | No | +| replacement | string | Replacement when the match succeeds. Placeholders like `$1`, `$2` can be used to represent the sub-matches in `regexp` | Yes | +| statusCode | int | Status code of response. Supported values: 301, 302, 303, 304, 307, 308. Default: 301. | No | +### Results +| Value | Description | +| ----- | ----------- | +| redirected | The request has been redirected | + +## GRPCProxy + +The `GRPCProxy` filter is a proxy for gRPC backend service. It supports both unary RPCs and streaming RPCs. + +Below is one of the simplest `GRPCProxy` configurations, it forwards incoming gRPC connections to `127.0.0.1:9095`. + +```yaml +kind: GRPCProxy +name: grpc-proxy-example-1 +pools: +- servers: + - url: http://127.0.0.1:9095 +``` + +Same as the `Proxy` filter: + +* a `filter` can be configured on a pool. +* the servers of a pool can be configured dynamically via service discovery. +* when there are multiple servers in a pool, the pool can do a load balance between them. + +Because gRPC does not support the http `Connect` method, it does not support tunneling mode, +we provide a new [load balancer](#proxyloadbalancespec) `policy.forward` to achieve a similar effect. + +Note that each gRPC client establishes a connection with Easegress. However, +Easegress may utilize a single connection when forwarding requests from various +clients to a gRPC server, due to its use of HTTP2. This action could potentially +disrupt some client or server applications. For instance, if the client +applications are structured to directly connect to the server, and both the +client and server have the ability to request a connection closure, then +problems may arise once Easegress is installed between them. If the server +wants to close the connection of one client, it closes the shared connection +with Easegress, thus affecting other clients. + +### Configuration + +| Name | Type | Description | Required | +| ------------ | ------------------------------------------------------ | --------------------------------------------------------------------------- | -------- | +| pools | [grpcproxy.ServerPoolSpec](#grpcproxyserverpoolspec) | The pool without `filter` is considered the main pool, other pools with `filter` are considered candidate pools, and a `GRPCProxy` must contain exactly one main pool. When a `GRPCProxy` gets a request, it first goes through the candidate pools, and if one of the pool's filter matches the request, servers of this pool handle the request, otherwise, the request is passed to the main pool. | Yes | +| timeout | string | The total time from easegress receive request to receive response, default is never timeout, only apply to unary calls. | No | +| borrowTimeout | string | Timeout of borrow a connection from pool. Default is never timeout. | No | +| connectTimeout | string | Timeout until a new connection is fully established. Default is never timeout. | No | +| maxIdleConnsPerHost | int | For a address, the maximum of connections allowed to create. Default value is 1024 | No | + +### Results + +| Value | Description | +|----------------|------------------------------| +| internalError | Encounters an internal error | +| clientError | Client-side error | +| serverError | Server-side error | + +## Common Types + +### pathadaptor.Spec + +| Name | Type | Description | Required | +| ------------ | ------------------------------------------------------ | --------------------------------------------------------------------------- | -------- | +| replace | string | Replaces request path with the value of this option when specified | No | +| addPrefix | string | Prepend the value of this option to request path when specified | No | +| trimPrefix | string | Trims the value of this option if request path start with it when specified | No | +| regexpReplace | [pathadaptor.RegexpReplace](#pathadaptorRegexpReplace) | Revise request path with regular expression | No | + +### pathadaptor.RegexpReplace + +| Name | Type | Description | Required | +| ------- | ------ | ----------------------------------------------------------------------------------------------------------------------- | -------- | +| regexp | string | Regular expression to match request path. The syntax of the regular expression is [RE2](https://golang.org/s/re2syntax) | Yes | +| replace | string | Replacement when the match succeeds. Placeholders like `$1`, `$2` can be used to represent the sub-matches in `regexp` | Yes | + +### httpheader.AdaptSpec + +Rules to revise request header. + +| Name | Type | Description | Required | +| ---- | ----------------- | ----------------------------------- | -------- | +| del | []string | Name of the headers to be removed | No | +| set | map[string]string | Name & value of headers to be set | No | +| add | map[string]string | Name & value of headers to be added | No | + +### proxy.ServerPoolSpec + +| Name | Type | Description | Required | +| --------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------ | -------- | +| spanName | string | Span name for tracing, if not specified, the `url` of the target server is used | No | +| serverTags | []string | Server selector tags, only servers have tags in this array are included in this pool | No | +| servers | [][proxy.Server](#proxyServer) | An array of static servers. If omitted, `serviceName` and `serviceRegistry` must be provided, and vice versa | No | +| serviceName | string | This option and `serviceRegistry` are for dynamic server discovery | No | +| serviceRegistry | string | This option and `serviceName` are for dynamic server discovery | No | +| loadBalance | [proxy.LoadBalance](#proxyLoadBalanceSpec) | Load balance options | Yes | +| memoryCache | [proxy.MemoryCacheSpec](#proxymemorycachespec) | Options for response caching | No | +| filter | [proxy.RequestMatcherSpec](#proxyrequestmatcherspec) | Filter options for candidate pools | No | +| serverMaxBodySize | int64 | Max size of response body, will use the option of the Proxy if not set. Responses with a body larger than this option are discarded. When this option is set to `-1`, Easegress takes the response body as a stream and the body can be any size, but some features are not possible in this case, please refer [Stream](./stream.md) for more information. | No | +| timeout | string | Request calceled when timeout | No | +| retryPolicy | string | Retry policy name | No | +| circuitBreakerPolicy | string | CircuitBreaker policy name | No | +| failureCodes | []int | Proxy return result of failureCode when backend resposne's status code in failureCodes. The default value is 5xx | No | + + +### proxy.Server + +| Name | Type | Description | Required | +| ------ | -------- | ------------------------------------------------------------------------------------------------------------ | -------- | +| url | string | Address of the server. The address should start with `http://` or `https://` (when used in the `WebSocketProxy`, it can also start with `ws://` and `wss://`), followed by the hostname or IP address of the server, and then optionally followed by `:{port number}`, for example: `https://www.megaease.com`, `http://10.10.10.10:8080`. When host name is used, the `Host` of a request sent to this server is always the hostname of the server, and therefore using a [RequestAdaptor](#requestadaptor) in the pipeline to modify it will not be possible; when IP address is used, the `Host` is the same as the original request, that can be modified by a [RequestAdaptor](#requestadaptor). See also `KeepHost`. | Yes | +| tags | []string | Tags of this server, refer `serverTags` in [proxy.PoolSpec](#proxyPoolSpec) | No | +| weight | int | When load balance policy is `weightedRandom`, this value is used to calculate the possibility of this server | No | +| keepHost | bool | If true, the `Host` is the same as the original request, no matter what is the value of `url`. Default value is `false`. | No | + +### proxy.LoadBalanceSpec + +| Name | Type | Description | Required | +| ------------- | ------ | ----------------------------------------------------------------------------------------------------------- | -------- | +| policy | string | Load balance policy, valid values are `roundRobin`, `random`, `weightedRandom`, `ipHash`, `headerHash` and `forward`, the last one is only used in `GRPCProxy` | Yes | +| headerHashKey | string | When `policy` is `headerHash`, this option is the name of a header whose value is used for hash calculation | No | +| stickySession | [proxy.StickySession](#proxyStickySessionSpec) | Sticky session spec | No | +| healthCheck | [proxy.HealthCheck](#proxyHealthCheckSpec) | Health check spec, note that healthCheck is not needed if you are using service registry | No | +| forwardKey | string | The value of this field is a header name of the incoming request, the value of this header is address of the target server (host:port), and the request will be sent to this address | No | + +### proxy.StickySessionSpec + +| Name | Type | Description | Required | +| ------------- | ------ | ----------------------------------------------------------------------------------------------------------- | -------- | +| mode | string | Mode of session stickiness, support `CookieConsistentHash`,`DurationBased`,`ApplicationBased` | Yes | +| appCookieName | string | Name of the application cookie, its value will be used as the session identifier for stickiness in `CookieConsistentHash` and `ApplicationBased` mode | No | +| lbCookieName | string | Name of the cookie generated by load balancer, its value will be used as the session identifier for stickiness in `DurationBased` and `ApplicationBased` mode, default is `EG_SESSION` | No | +| lbCookieExpire | string | Expire duration of the cookie generated by load balancer, its value will be used as the session expire time for stickiness in `DurationBased` and `ApplicationBased` mode, default is 2 hours | No | + +### proxy.HealthCheckSpec + +| Name | Type | Description | Required | +| ------------- | ------ | ----------------------------------------------------------------------------------------------------------- | -------- | +| interval | string | Interval duration for health check, default is 60s | Yes | +| path | string | Path URL for server health check | No | +| timeout | string | Timeout duration for health check, default is 3s | No | +| fails | int | Consecutive fails count for assert fail, default is 1 | No | +| passes | int | Consecutive passes count for assert pass , default is 1 | No | + +### proxy.MemoryCacheSpec + +| Name | Type | Description | Required | +| ------------- | -------- | ------------------------------------------------------------------------------ | -------- | +| codes | []int | HTTP status codes to be cached | Yes | +| expiration | string | Expiration duration of cache entries | Yes | +| maxEntryBytes | uint32 | Maximum size of the response body, response with a larger body is never cached | Yes | +| methods | []string | HTTP request methods to be cached | Yes | + +### proxy.RequestMatcherSpec + +Polices: +- If the policy is empty or `general`, matcher match requests with `headers` and `urls`. +- If the policy is `ipHash`, the matcher match requests if their IP hash value is less than `permil``. +- If the policy is `headerHash`, the matcher match requests if their header hash value is less than `permil`, use the key of `headerHashKey`. +- If the policy is `random`, the matcher matches requests with probability `permil`/1000. + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| policy | string | Policy used to match requests, support `general`, `ipHash`, `headerHash`, `random` | No | +| headers | map[string][StringMatcher](#stringmatcher) | Request header filter options. The key of this map is header name, and the value of this map is header value match criteria | No | +| urls | [][proxy.MethodAndURLMatcher](#proxyMethodAndURLMatcher) | Request URL match criteria | No | +| permil | uint32 | the probability of requests been matched. Value between 0 to 1000 | No | +| matchAllHeaders | bool | All rules in headers should be match | No | +| headerHashKey | string | Used by policy `headerHash`. | No | + +### grpcproxy.ServerPoolSpec + +| Name | Type | Description | Required | +| --------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------ | -------- | +| spanName | string | Span name for tracing, if not specified, the `url` of the target server is used | No | +| serverTags | []string | Server selector tags, only servers have tags in this array are included in this pool | No | +| servers | [][proxy.Server](#proxyServer) | An array of static servers. If omitted, `serviceName` and `serviceRegistry` must be provided, and vice versa | No | +| serviceName | string | This option and `serviceRegistry` are for dynamic server discovery | No | +| serviceRegistry | string | This option and `serviceName` are for dynamic server discovery | No | +| loadBalance | [proxy.LoadBalance](#proxyLoadBalanceSpec) | Load balance options | Yes | +| filter | [grpcproxy.RequestMatcherSpec](#grpcproxyrequestmatcherspec) | Filter options for candidate pools | No | +| circuitBreakerPolicy | string | CircuitBreaker policy name | No | + + +### grpcproxy.RequestMatcherSpec + +Polices: +- If the policy is empty or `general`, matcher match requests with `headers`, `urls` and `methods`. +- If the policy is `ipHash`, the matcher match requests if their IP hash value is less than `permil``. +- If the policy is `headerHash`, the matcher match requests if their header hash value is less than `permil`, use the key of `headerHashKey`. +- If the policy is `random`, the matcher matches requests with probability `permil`/1000. + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| policy | string | Policy used to match requests, support `general`, `ipHash`, `headerHash`, `random` | No | +| headers | map[string][StringMatcher](#stringmatcher) | Request header filter options. The key of this map is header name, and the value of this map is header value match criteria | No | +| urls | [][proxy.MethodAndURLMatcher](#proxyMethodAndURLMatcher) | Request URL match criteria | No | +| permil | uint32 | the probability of requests been matched. Value between 0 to 1000 | No | +| matchAllHeaders | bool | All rules in headers should be match | No | +| headerHashKey | string | Used by policy `headerHash`. | No | +| methods | [][StringMatcher](#stringmatcher) | Method name filter options. | No | + + +### StringMatcher + +The relationship between `exact`, `prefix`, and `regex` is `OR`. + +| Name | Type | Description | Required | +| ------ | ------ | --------------------------------------------------------------------------- | -------- | +| exact | string | The string must be identical to the value of this field. | No | +| prefix | string | The string must begin with the value of this field | No | +| regex | string | The string must the regular expression specified by the value of this field | No | +| empty | bool | The string must be empty | No | + +### proxy.MethodAndURLMatcher + +The relationship between `methods` and `url` is `AND`. + +| Name | Type | Description | Required | +| ------- | ------------------------------------------ | ---------------------------------------------------------------- | -------- | +| methods | []string | HTTP method criteria, Default is an empty list means all methods | No | +| url | [StringMatcher](#stringmatcher) | Criteria to match a URL | Yes | + +### urlrule.URLRule + +The relationship between `methods` and `url` is `AND`. + +| Name | Type | Description | Required | +| --------- | ------------------------------------------ | ---------------------------------------------------------------- | -------- | +| methods | []string | HTTP method criteria, Default is an empty list means all methods | No | +| url | [StringMatcher](#StringMatcher) | Criteria to match a URL | Yes | +| policyRef | string | Name of resilience policy for matched requests | No | + + +### proxy.Compression + +| Name | Type | Description | Required | +| --------- | ---- | --------------------------------------------------------------------------------------------- | -------- | +| minLength | int | Minimum response body size to be compressed, response with a smaller body is never compressed | Yes | + +### proxy.MTLS +| Name | Type | Description | Required | +| -------------- | ------ | ------------------------------ | -------- | +| certBase64 | string | Base64 encoded certificate | Yes | +| keyBase64 | string | Base64 encoded key | Yes | +| rootCertBase64 | string | Base64 encoded root certificate | Yes | +| insecureSkipVerify| bool | insecureSkipVerify controls whether a client verifies the server's certificate chain and host name. If insecureSkipVerify is true, crypto/tls accepts any certificate presented by the server and any host name in that certificate. In this mode, TLS is susceptible to machine-in-the-middle attacks unless custom verification is used. This should be used only for testing or in combination with VerifyConnection or VerifyPeerCertificate. | No | + +### websocketproxy.WebSocketServerPoolSpec + +| Name | Type | Description | Required | +| --------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------ | -------- | +| serverTags | []string | Server selector tags, only servers have tags in this array are included in this pool | No | +| servers | [][proxy.Server](#proxyServer) | An array of static servers. If omitted, `serviceName` and `serviceRegistry` must be provided, and vice versa | No | +| serviceName | string | This option and `serviceRegistry` are for dynamic server discovery | No | +| serviceRegistry | string | This option and `serviceName` are for dynamic server discovery | No | +| serverMaxMsgSize | int | Max server message size, default is 32768. | No | +| clientMaxMsgSize | int | Max client message size, default is 32768. | No | +| loadBalance | [proxy.LoadBalance](#proxyLoadBalanceSpec) | Load balance options | Yes | +| filter | [proxy.RequestMatcherSpec](#proxyrequestmatcherspec) | Filter options for candidate pools | No | +| insecureSkipVerify | bool | Disable origin verification when accepting client connections, default is `false`. | No | +| originPatterns | []string | Host patterns for authorized origins, used to enable cross origin WebSockets. | No | + +### mock.Rule + +| Name | Type | Description | Required | +| ---------- | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | +| code | int | HTTP status code of the mocked response | Yes | +| match | [MatchRule](#mock.MatchRule) | Rule to match a request | Yes | +| delay | string | Delay duration, for the request processing time mocking | No | +| headers | map[string]string | Headers of the mocked response | No | +| body | string | Body of the mocked response, default is an empty string | No | + +### mock.MatchRule + +| Name | Type | Description | Required | +| ---------- | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | +| path | string | Path match criteria, if request path is the value of this option, then the response of the request is mocked according to this rule | No | +| pathPrefix | string | Path prefix match criteria, if request path begins with the value of this option, then the response of the request is mocked according to this rule | No | +| matchAllHeaders | bool | Whether to match all headers | No | +| headers | map[string][StringMatcher](#stringmatcher) | Headers to match, key is a header name, value is the rule to match the header value | No | + + +### ratelimiter.Policy + +| Name | Type | Description | Required | +| ------------------ | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | +| name | string | Name of the policy. Must be unique in one RateLimiter configuration | Yes | +| timeoutDuration | string | Maximum duration a request waits for permission to pass through the RateLimiter. The request fails if it cannot get permission in this duration. Default is 100ms | No | +| limitRefreshPeriod | string | The period of a limit refresh. After each period the RateLimiter sets its permissions count back to the `limitForPeriod` value. Default is 10ms | No | +| limitForPeriod | int | The number of permissions available in one `limitRefreshPeriod`. Default is 50 | No | + +### httpheader.ValueValidator + +| Name | Type | Description | Required | +| ------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | +| values | []string | An array of strings, if one of the header values of any header of the request is found in the array, the request is considered to pass the validation of current rule | No | +| regexp | string | A regular expression, if one of the header values of any header of the request matches this regular expression, the request is considered to pass the validation of current rule | No | + +### validator.JWTValidatorSpec + +| Name | Type | Description | Required | +|------------|--------|--------------------------------------------------------------------------------------------------------------------------------------------------------|----------| +| cookieName | string | The name of a cookie, if this option is set and the cookie exists, its value is used as the token string, otherwise, the `Authorization` header is used | No | +| algorithm | string | The algorithm for validation:`HS256`,`HS384`,`HS512`,`RS256`,`RS384`,`RS512`,`ES256`,`ES384`,`ES512`,`EdDSA` are supported | Yes | + | publicKey | string | The public key is used for `RS256`,`RS384`,`RS512`,`ES256`,`ES384`,`ES512` or `EdDSA` validation in hex encoding | Yes | +| secret | string | The secret is for `HS256`,`HS384`,`HS512` validation in hex encoding | Yes | + +### validator.BasicAuthValidatorSpec + +| Name | Type | Description | Required | +|--------------|--------|------------------------------------------------------------------------------------------------------------------------------------------------------|----------| +| mode | string | The mode of basic authentication, valid values are `FILE`, `ETCD` and `LDAP` | Yes | +| userFile | string | The user file used for `FILE` mode | No | +| etcdPrefix | string | The etcd prefix used for `ETCD` mode | No | +| ldap | [basicAuth.LDAPSpec](#basicAuthLDAPSpec) | The LDAP configuration used for `LDAP` mode | No | + +### basicAuth.LDAPSpec + +| Name | Type | Description | Required | +|--------------|--------|-------------------------------------------------------------------------|----------| +| host | string | The host of the LDAP server | Yes | +| port | int | The port of the LDAP server | Yes | +| baseDN | string | The base dn of the LDAP server, e.g. `ou=users,dc=example,dc=org` | Yes | +| uid | string | The user attribute used to bind user, e.g. `cn` | Yes | +| useSSL | bool | Whether to use SSL | No | +| skipTLS | bool | Whether to skip `StartTLS` | No | +| insecure | bool | Whether to skip verifying LDAP server's certificate chain and host name | No | +| serverName | string | Server name used to verify certificate when `insecure` is `false` | No | +| certBase64 | string | Base64 encoded certificate | No | +| keyBase64 | string | Base64 encoded key | No | + +### signer.Spec + +| Name | Type | Description | Required | +| ----------- | -------------------------------- | ------------------------------------------------------------------------- | -------- | +| literal | [signer.Literal](#signerLiteral) | Literal strings for customization, default value is used if omitted | No | +| excludeBody | bool | Exclude request body from the signature calculation, default is `false` | No | +| ttl | string | Time to live of a signature, default is 0 means a signature never expires | No | +| accessKeys | map[string]string | A map of access key id to access key secret | Yes | +| accessKeyId | string | ID used to set credential | No | +| accessKeySecret | string | Value usd to set credential | No | +| ignoredHeaders | []string | Headers to be ignored | No | +| headerHoisting | signer.HeaderHoisting | HeaderHoisting defines which headers are allowed to be moved from header to query in presign: header with name has one of the allowed prefixes, but hasn't any disallowed prefixes and doesn't match any of disallowed names are allowed to be hoisted | No | + +### signer.HeaderHoisting + +| Name | Type | Description | Required | +|------------------|----------|-------------------------------|----------| +| allowedPrefix | []string | Allowed prefix for headers | No | +| disallowedPrefix | []string | Disallowed prefix for headers | No | +| disallowed | []string | Disallowed headers | No | + +### signer.Literal + +| Name | Type | Description | Required | +| ---------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | +| scopeSuffix | string | The last part to build credential scope, default is `request`, in `Amazon Signature V4`, it is `aws4_request` | No | +| algorithmName | string | The query name of the signature algorithm in the request, default is `X-Algorithm`, in `Amazon Signature V4`, it is `X-Amz-Algorithm` | No | +| algorithmValue | string | The header/query value of the signature algorithm for the request, default is "HMAC-SHA256", in `Amazon Signature V4`, it is `AWS4-HMAC-SHA256` | No | +| signedHeaders | string | The header/query headers of the signed headers, default is `X-SignedHeaders`, in `Amazon Signature V4`, it is `X-Amz-SignedHeaders` | No | +| signature | string | The query name of the signature, default is `X-Signature`, in `Amazon Signature V4`, it is `X-Amz-Signature` | No | +| date | string | The header/query name of the request time, default is `X-Date`, in `Amazon Signature V4`, it is `X-Amz-Date` | No | +| expires | string | The query name of expire duration, default is `X-Expires`, in `Amazon Signature V4`, it is `X-Amz-Date` | No | +| credential | string | The query name of credential, default is `X-Credential`, in `Amazon Signature V4`, it is `X-Amz-Credential` | No | +| contentSha256 | string | The header name of body/payload hash, default is `X-Content-Sha256`, in `Amazon Signature V4`, it is `X-Amz-Content-Sha256` | No | +| signingKeyPrefix | string | The prefix is prepended to access key secret when deriving the signing key, default is an empty string, in `Amazon Signature V4`, it is `AWS4` | No | + +### validator.OAuth2ValidatorSpec + +| Name | Type | Description | Required | +| --------------- | ------------------------------------------------------------------ | ------------------------------------------------- | -------- | +| tokenIntrospect | [validator.OAuth2TokenIntrospect](#validatorOAuth2TokenIntrospect) | Configuration for Token Introspection mode | No | +| jwt | [validator.OAuth2JWT](#validatorOAuth2JWT) | Configuration for Self-Encoded Access Tokens mode | No | + +### validator.OAuth2TokenIntrospect + +| Name | Type | Description | Required | +| ------------ | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | +| endPoint | string | The endpoint of the token introspection server | Yes | +| clientId | string | Client id of Easegress in the token introspection server | No | +| clientSecret | string | Client secret of Easegress | No | +| basicAuth | string | If `clientId` not specified and this option is specified, its value is used for basic authorization with the token introspection server | No | +| insecureTls | bool | Whether the connection between Easegress and the token introspection server need to be secure or not, default is `false` means the connection need to be a secure one | No | + +### validator.OAuth2JWT + +| Name | Type | Description | Required | +| --------- | ------ | ------------------------------------------------------------------------ | -------- | +| algorithm | string | The algorithm for validation, `HS256`, `HS384` and `HS512` are supported | Yes | +| secret | string | The secret for validation, in hex encoding | Yes | + +### kafka.Topic + +| Name | Type | Description | Required | +| --------- | ------ | ------------------------------------------------------------------------ | -------- | +| default | string | Default topic for Kafka backend | Yes | +| dynamic.header | string | The HTTP header that contains Kafka topic | Yes | + +### headertojson.HeaderMap + +| Name | Type | Description | Required | +| --------- | ------ | ------------------------------------------------------------------------ | -------- | +| header | string | The HTTP header that contains JSON value | Yes | +| json | string | The field name to put JSON value into HTTP body | Yes | + + +### headerlookup.HeaderSetterSpec +| Name | Type | Description | Required | +|------|------|-------------|----------| +| etcdKey | string | Key used to get data | No | +| headerKey | string | Key used to set data into http header | No | + +### requestadaptor.SignerSpec + +This type is derived from [signer.Spec](#signerspec), with the following +two more fields. + +| Name | Type | Description | Required | +|------|------|-------------|----------| +| apiProvider | string | The RequestAdaptor pre-defines the [Literal](#signerliteral) and [HeaderHoisting](#signerheaderhoisting) configuration for some API providers, specify the provider name in this field to use one of them, only `aws4` is supported at present. | No | +| scopes | []string | Scopes of the input request | No | + +### Template Of Builder Filters + +The content of the `template` field in the builder filters' spec is a +a template defined in Golang [text/template](https://pkg.go.dev/text/template), +with extra functions from the [sprig](https://go-task.github.io/slim-sprig/) +package, and extra functions defined by Easegress: + +* **addf**: calculate the sum of the input two numbers. +* **subf**: calculate the difference of the two input numbers. +* **mulf**: calculate the product of the two input numbers. +* **divf**: calculate the quotient of the two input numbers. +* **log**: write a log message to Easegress log, the first argument must be + `debug`, `info`, `warn` or `error`, and the second argument is the message. +* **mergeObject**: merge two or more objects into one, the type of the input + objects must be `map[string]interface{}`, and if one of their field is + also an object, its type must also be `map[string]interface{}`. +* **jsonEscape**: escape a string so that it can be used as the key or value + in JSON text. + +Easegress injects existing requests/responses of the current context into +the template engine at runtime, so we can use `.requests..` +or `.responses..` to read the information out (the +available fields vary from the protocol of the request or response, +and please refer [Pipeline](controllers.md#pipeline) for what is `namespace`). +For example, if the request of the `DEFAULT` namespace is an HTTP one, we +can access its method via `.requests.DEFAULT.Method`. + +Easegress also injects other data into the template engine, which can be +accessed with `.data.`, for example, we can use `.data.PIPELINE` to +read the data defined in the pipeline spec. + +The `template` should generate a string in YAML format, the schema of the +result YAML varies from filters and protocols. + +#### HTTP Specific + +* **Available fields of existing requests** + + All exported fields of the [http.Request](https://pkg.go.dev/net/http#Request). + And `RawBody` is the body as bytes; `Body` is the body as string; `JSONBody` + is the body as a JSON object; `YAMLBody` is the body as a YAML object. + +* **Available fields of existing responses** + + All exported fields of the [http.Response](https://pkg.go.dev/net/http#Response). + And `RawBody` is the body as bytes; `Body` is the body as string; `JSONBody` + is the body as a JSON object; `YAMLBody` is the body as a YAML object. + +* **Schema of result request** + + | Name | Type | Description | Required | + |------|------|-------------|----------| + | method | string | HTTP Method of the result request, default is `GET`. | No | + | url | string | URL of the result request, default is `/`. | No | + | headers | map[string][]string | Headers of the result request. | No | + | body | string | Body of the result request. | No | + | formData | map[string]field | Body of the result request, in form data pattern. | No | + + Please note `body` takes higher priority than `formData`, and the schema of + `field` in `formData` is: + + | Name | Type | Description | Required | + |----------|--------|---------------------|----------| + | value | string | value of the field. | No | + | fileName | string | the file name, if value is the content of a file. | No | + +* **Schema of result response** + + | Name | Type | Description | Required | + |------|------|-------------|----------| + | statusCode | int | HTTP status code, default is 200. | No | + | headers | map[string][]string | Headers of the result request. | No | + | body | string | Body of the result request. | No | + +* **Schema of RequestAdaptor** + +| Name | Type | Description | Required | +|------|------|-------------|----------| +| method | string | If provided, the method of the original request is replaced by the value of this option | No | +| path | [pathadaptor.Spec](#pathadaptorSpec) | Rules to revise request path | No | +| header | [httpheader.AdaptSpec](#httpheaderAdaptSpec) | Rules to revise request header | No | +| body | string | If provided the body of the original request is replaced by the value of this option. | No | +| host | string | If provided the host of the original request is replaced by the value of this option. | No | + +* **Schema of ResponseAdaptor** + +| Name | Type | Description | Required | +|------|------|-------------|----------| +| header | [httpheader.AdaptSpec](#httpheaderAdaptSpec) | Rules to revise request header | No | +| body | string | If provided the body of the original request is replaced by the value of this option. | No | \ No newline at end of file From 68ae6e74010cf79676f3d2ea5001e04330626439 Mon Sep 17 00:00:00 2001 From: chen Date: Fri, 8 Sep 2023 16:15:16 +0800 Subject: [PATCH 08/22] fix grpcserver doc in reference --- docs/07.Reference/7.1.Controllers.md | 129 +++++++++++++++++---------- 1 file changed, 81 insertions(+), 48 deletions(-) diff --git a/docs/07.Reference/7.1.Controllers.md b/docs/07.Reference/7.1.Controllers.md index f49b3fea46..b8efe55760 100644 --- a/docs/07.Reference/7.1.Controllers.md +++ b/docs/07.Reference/7.1.Controllers.md @@ -1,51 +1,53 @@ -# Controllers - -- [Controllers](#controllers) - - [System Controllers](#system-controllers) - - [ServiceRegistry](#serviceregistry) - - [TrafficController](#trafficcontroller) - - [RawConfigTrafficController](#rawconfigtrafficcontroller) - - [HTTPServer](#httpserver) - - [gRRC Service Proxy](#grrc-service-proxy) - - [Simple Proxy Configuration](#simple-proxy-configuration) - - [Configuration](#configuration) - - [AccessLogVariable](#accesslogvariable) - - [Pipeline](#pipeline) - - [StatusSyncController](#statussynccontroller) - - [Business Controllers](#business-controllers) - - [GlobalFilter](#globalfilter) - - [EaseMonitorMetrics](#easemonitormetrics) - - [FaaSController](#faascontroller) - - [IngressController](#ingresscontroller) - - [ConsulServiceRegistry](#consulserviceregistry) - - [EtcdServiceRegistry](#etcdserviceregistry) - - [EurekaServiceRegistry](#eurekaserviceregistry) - - [ZookeeperServiceRegistry](#zookeeperserviceregistry) - - [NacosServiceRegistry](#nacosserviceregistry) - - [AutoCertManager](#autocertmanager) - - [Common Types](#common-types) - - [tracing.Spec](#tracingspec) - - [spanlimits.Spec](#spanlimitsspec) - - [batchlimits.Spec](#batchlimitsspec) - - [exporter.Spec](#exporterspec) - - [jaeger.Spec](#jaegerspec) - - [zipkin.Spec](#zipkinspec) - - [otlp.Spec](#otlpspec) - - [zipkin.DeprecatedSpec](#zipkindeprecatedspec) - - [ipfilter.Spec](#ipfilterspec) - - [httpserver.Rule](#httpserverrule) - - [httpserver.Host](#httpserverhost) - - [httpserver.Path](#httpserverpath) - - [httpserver.Header](#httpserverheader) - - [pipeline.Spec](#pipelinespec) - - [pipeline.FlowNode](#pipelineflownode) - - [filters.Filter](#filtersfilter) - - [easemonitormetrics.Kafka](#easemonitormetricskafka) - - [nacos.ServerSpec](#nacosserverspec) - - [autocertmanager.DomainSpec](#autocertmanagerdomainspec) - - [resilience.Policy](#resiliencepolicy) - - [Retry Policy](#retry-policy) - - [CircuitBreaker Policy](#circuitbreaker-policy) +# Controllers + +- [System Controllers](#system-controllers) + - [ServiceRegistry](#serviceregistry) + - [TrafficController](#trafficcontroller) + - [RawConfigTrafficController](#rawconfigtrafficcontroller) + - [HTTPServer](#httpserver) + - [gRRC Service Proxy](#grrc-service-proxy) + - [Simple Proxy Configuration](#simple-proxy-configuration) + - [Configuration](#configuration) + - [AccessLogVariable](#accesslogvariable) + - [Pipeline](#pipeline) + - [StatusSyncController](#statussynccontroller) +- [Business Controllers](#business-controllers) + - [GlobalFilter](#globalfilter) + - [EaseMonitorMetrics](#easemonitormetrics) + - [FaaSController](#faascontroller) + - [IngressController](#ingresscontroller) + - [ConsulServiceRegistry](#consulserviceregistry) + - [EtcdServiceRegistry](#etcdserviceregistry) + - [EurekaServiceRegistry](#eurekaserviceregistry) + - [ZookeeperServiceRegistry](#zookeeperserviceregistry) + - [NacosServiceRegistry](#nacosserviceregistry) + - [AutoCertManager](#autocertmanager) +- [Common Types](#common-types) + - [tracing.Spec](#tracingspec) + - [spanlimits.Spec](#spanlimitsspec) + - [batchlimits.Spec](#batchlimitsspec) + - [exporter.Spec](#exporterspec) + - [jaeger.Spec](#jaegerspec) + - [zipkin.Spec](#zipkinspec) + - [otlp.Spec](#otlpspec) + - [zipkin.DeprecatedSpec](#zipkindeprecatedspec) + - [ipfilter.Spec](#ipfilterspec) + - [httpserver.Rule](#httpserverrule) + - [httpserver.Host](#httpserverhost) + - [httpserver.Path](#httpserverpath) + - [httpserver.Header](#httpserverheader) + - [pipeline.Spec](#pipelinespec) + - [pipeline.FlowNode](#pipelineflownode) + - [filters.Filter](#filtersfilter) + - [grpcserver.Rule](#grpcserverrule) + - [grpcserver.Method](#grpcservermethod) + - [grpcserver.Header](#grpcserverheader) + - [easemonitormetrics.Kafka](#easemonitormetricskafka) + - [nacos.ServerSpec](#nacosserverspec) + - [autocertmanager.DomainSpec](#autocertmanagerdomainspec) + - [resilience.Policy](#resiliencepolicy) + - [Retry Policy](#retry-policy) + - [CircuitBreaker Policy](#circuitbreaker-policy) As the [architecture diagram](../imgs/architecture.png) shows, the controller is the core entity to control kinds of working. There are two kinds of controllers overall: @@ -154,6 +156,8 @@ The below parameters will help manage connections better | maxConnectionAgeGrace | duration | An additive period after MaxConnectionAge after which the connection will be forcibly closed. default value is infinity | No | | keepaliveTime | duration | After a duration of this time if the server doesn't see any activity it pings the client to see if the transport is still alive. If set below 1s, a minimum value of 1s will be used instead. default value is 2 hours. | No | | keepaliveTimeout | duration | After having pinged for keepalive check, the server waits for a duration of Timeout and if no activity is seen even after that the connection is closed. default value is 20 seconds |No | +| ipFilter | [ipfilter.Spec](#ipfilterSpec) | IP Filter for all traffic | No | +| rules | [][grpcserver.Rule](#grpcserverrule) | Router rules | No | ### AccessLogVariable @@ -708,6 +712,35 @@ The self-defining specification of each filter references to [filters](./filters | kind | string | Kind of filter | Yes | | [self-defining fields](./filters.md) | - | - | - | +### grpcserver.Rule + +| Name | Type | Description | Required | +|------|------|-------------|----------| +| host | string | Exact host to match | No | +| hostRegexp | string | Host in regular expression to match | No | +| methods | [][grpcserver.Method](#grpcservermethod) | Method matching rules, empty means to match nothing. | No | +| ipFilter | [ipFilter.Spec](#ipfilterspec) | IP Filter for all traffic under the rule | No | + +### grpcserver.Method + +| Name | Type | Description | Required | +|------|------|-------------|----------| +| method | string | Exact method to match | No | +| methodPrefix | string | Prefix of the method to match | No | +| methodRegexp | string | Method in regular expression to match | No | +| backend | string | backend name (pipeline name in static config, service name in mesh) | No | +| headers | [][grpcserver.Header](#grpcserverheader) | Headers to match | No | +| matchAllHeader | bool | Match all headers that are defined in headers, default is false. | No | +| ipFilter | [ipFilter.Spec](#ipfilterspec) | IP Filter for all traffic under the method | No | + +### grpcserver.Header + +| Name | Type | Description | Required | +|------|------|-------------|----------| +| key | string | Header key to match | Yes | +| values | []string | Header values to match | No | +| regexp | string | Header value in regular expression to match | No | + ### easemonitormetrics.Kafka | Name | Type | Description | Required | From 6eda574bd46519534a05ae9256d2da617f619d17 Mon Sep 17 00:00:00 2001 From: chen Date: Fri, 8 Sep 2023 17:28:49 +0800 Subject: [PATCH 09/22] finish grpc doc in tutorial --- docs/02.Tutorials/2.7.gRPC.md | 110 ++++++++++++++++++++++----- docs/07.Reference/7.1.Controllers.md | 65 ++++++++++++---- docs/07.Reference/7.2.Filters.md | 17 +++-- 3 files changed, 154 insertions(+), 38 deletions(-) diff --git a/docs/02.Tutorials/2.7.gRPC.md b/docs/02.Tutorials/2.7.gRPC.md index dab4965cd5..1dd9fc03ad 100644 --- a/docs/02.Tutorials/2.7.gRPC.md +++ b/docs/02.Tutorials/2.7.gRPC.md @@ -1,27 +1,103 @@ -# gRPC +# gRPC -#### gRRC Service Proxy +- [GRPCServer](#grpcserver) + - [Example Requests](#example-requests) +- [GRPCProxy](#grpcproxy) -Easegress gRPC servers is a proxy server base on gRPC protocol. +## GRPCServer + +The `GRPCServer` in Easegress provides robust functionality tailored to gRPC protocol interactions. With its IP filtering feature, traffic can be selectively allowed or blocked, ensuring that only desired clients can communicate with the services. Additionally, the server's routing rules offer flexible methods to determine how each incoming request is processed and forwarded, based on host, method, headers, and other criteria. -##### Simple Proxy Configuration -Below is one of the simplest gRPC Proxy Server, and it will listen on port `8080` and handle the requests that includes kv `content-type:application/grpc` in headers by use backend `pipeline-grpc-forward` ``` yaml name: server-grpc kind: GRPCServer - port: 8080 -maxConnections: 1024 -maxConnectionIdle: 60s -ipFilter: {} +# The maximum number of connections allowed by gRPC Server. +# Default value 10240 +maxConnections: 10240 + +# IP Filter for all traffic under the server +ipFilter: + blockIPs: [] + allowIPs: [] + blockByDefault: false +# routing rules rules: - - methods: - - backend: pipeline-grpc - headers: - - key: "Content-Type" - values: - - "application/grpc" -xForwardedFor: true -``` \ No newline at end of file +# Rules for host matching. +# If not match, GRPCServer will check next rule. +- host: + hostRegexp: + + methods: + - method: /Sale/AddProduct # Exact method match + backend: sale-pipeline + + - methodPrefix: /IT # Matches method with the given prefix + backend: it-pipeline + + - headers: # Matches by header + - key: x-geo-country + values: ["CN", "EU", "US"] + - key: user-agent + values: ["SaleClient/1.0.0"] + matchAllHeader: false + backend: header-pipeline + + - methodRegexp: .* # Match by regexp + backend: other-pipeline + +# more rules +- methods: + ... +``` + +For an in-depth exploration of the GRPCServer settings, please refer to the [GRPCServer](../07.Reference/7.1.Controllers.md#grpcserver). + + +### Example Requests +- A gRPC request with method `/Sale/AddProduct` will be routed to the `sale-pipeline`. +- Any request with a method starting with `/IT` (e.g., `/IT/UpdateSoftware`) will be directed to the `it-pipeline`. +- If a client sends a request with headers `x-geo-country` set to `CN` and `user-agent` set to `SaleClient/1.0.0`, it will be handled by the `header-pipeline`. +- All other requests (due to the wildcard `methodRegexp`) will be sent to the `other-pipeline`. + +## GRPCProxy + +The `GRPCProxy` filter in Easegress serves as a specialized proxy designed for gRPC backend services. This filter facilitates both unary and streaming RPCs, ensuring seamless communication in a gRPC ecosystem. + +Here's a basic configuration of the `GRPCProxy` filter that directs incoming gRPC connections to two backend servers, `192.168.1.1:80` and `192.168.1.2:80`: + +```yaml +name: demo-pipeline +kind: Pipeline +flow: +- proxy + +filters: +- kind: GRPCProxy + name: proxy + pools: + - loadBalance: + policy: roundRobin + servers: + - url: http://192.168.1.1:80 + - url: http://192.168.1.2:80 +``` + +Same as the `Proxy` filter: + +* a `filter` can be configured on a pool. +* the servers of a pool can be configured dynamically via service discovery. +* when there are multiple servers in a pool, the pool can do a load balance between them. + +Because gRPC does not support the http `Connect` method, it does not support tunneling mode, +we provide a new [load balancer](../07.Reference/7.2.Filters.md#proxyloadbalancespec) `policy.forward` to achieve a similar effect. + +Note that each gRPC client establishes a connection with Easegress. +However, Easegress may utilize a single connection when forwarding requests from various clients to a gRPC server, due to its use of HTTP2. +This action could potentially disrupt some client or server applications. +For instance, if the client applications are structured to directly connect to the server, and both the client and server have the ability to request a connection closure, then problems may arise once Easegress is installed between them. +If the server wants to close the connection of one client, it closes the shared connection with Easegress, thus affecting other clients. + +For more details about [GRPCProxy](../07.Reference/7.2.Filters.md#grpcproxy) filter. diff --git a/docs/07.Reference/7.1.Controllers.md b/docs/07.Reference/7.1.Controllers.md index b8efe55760..55cab02587 100644 --- a/docs/07.Reference/7.1.Controllers.md +++ b/docs/07.Reference/7.1.Controllers.md @@ -5,9 +5,7 @@ - [TrafficController](#trafficcontroller) - [RawConfigTrafficController](#rawconfigtrafficcontroller) - [HTTPServer](#httpserver) - - [gRRC Service Proxy](#grrc-service-proxy) - - [Simple Proxy Configuration](#simple-proxy-configuration) - - [Configuration](#configuration) + - [GRPCServer](#grpcserver) - [AccessLogVariable](#accesslogvariable) - [Pipeline](#pipeline) - [StatusSyncController](#statussynccontroller) @@ -123,27 +121,62 @@ rules: | globalFilter | string | Name of [GlobalFilter](#globalfilter) for all backends | No | | accessLogFormat | string | Format of access log, default is `[{{Time}}] [{{RemoteAddr}} {{RealIP}} {{Method}} {{URI}} {{Proto}} {{StatusCode}}] [{{Duration}} rx:{{ReqSize}}B tx:{{RespSize}}B] [{{Tags}}]`, variable is delimited by "{{" and "}}", please refer [Access Log Variable](#accesslogvariable) for all built-in variables | No | -#### gRRC Service Proxy +#### GRPCServer -Easegress gRPC servers is a proxy server base on gRPC protocol. +The `GRPCServer` in Easegress provides robust functionality tailored to gRPC protocol interactions. With its IP filtering feature, traffic can be selectively allowed or blocked, ensuring that only desired clients can communicate with the services. Additionally, the server's routing rules offer flexible methods to determine how each incoming request is processed and forwarded, based on host, method, headers, and other criteria. -##### Simple Proxy Configuration -Below is one of the simplest gRPC Proxy Server, and it will listen on port `8080` and handle the requests that includes kv `content-type:application/grpc` in headers by use backend `pipeline-grpc-forward` ``` yaml +name: server-grpc kind: GRPCServer port: 8080 -name: server-grpc + +# The maximum number of connections allowed by gRPC Server. +# Default value 10240 +maxConnections: 10240 + +# IP Filter for all traffic under the server +ipFilter: + blockIPs: [] + allowIPs: [] + blockByDefault: false + +# routing rules rules: - - methods: - - backend: pipeline-grpc - headers: - - key: "Content-Type" - values: - - "application/grpc" -xForwardedFor: true +# Rules for host matching. +# If not match, GRPCServer will check next rule. +- host: + hostRegexp: + + methods: + - method: /Sale/AddProduct # Exact method match + backend: sale-pipeline + + - methodPrefix: /IT # Matches method with the given prefix + backend: it-pipeline + + - headers: # Matches by header + - key: x-geo-country + values: ["CN", "EU", "US"] + - key: user-agent + values: ["SaleClient/1.0.0"] + matchAllHeader: false + backend: header-pipeline + + - methodRegexp: .* # Match by regexp + backend: other-pipeline + +# more rules +- methods: + ... ``` -##### Configuration +##### Example Requests +- A gRPC request with method `/Sale/AddProduct` will be routed to the `sale-pipeline`. +- Any request with a method starting with `/IT` (e.g., `/IT/UpdateSoftware`) will be directed to the `it-pipeline`. +- If a client sends a request with headers `x-geo-country` set to `CN` and `user-agent` set to `SaleClient/1.0.0`, it will be handled by the `header-pipeline`. +- All other requests (due to the wildcard `methodRegexp`) will be sent to the `other-pipeline`. + +##### Configuration The below parameters will help manage connections better | Name | Type | Description | Required | diff --git a/docs/07.Reference/7.2.Filters.md b/docs/07.Reference/7.2.Filters.md index 6ed9562ea2..64f3df390e 100644 --- a/docs/07.Reference/7.2.Filters.md +++ b/docs/07.Reference/7.2.Filters.md @@ -1370,11 +1370,18 @@ The `GRPCProxy` filter is a proxy for gRPC backend service. It supports both una Below is one of the simplest `GRPCProxy` configurations, it forwards incoming gRPC connections to `127.0.0.1:9095`. ```yaml -kind: GRPCProxy -name: grpc-proxy-example-1 -pools: -- servers: - - url: http://127.0.0.1:9095 +name: demo-pipeline +kind: Pipeline + +flow: +- filter: proxy + +filters: +- name: proxy + kind: GRPCProxy + pools: + - servers: + - url: http://127.0.0.1:9095 ``` Same as the `Proxy` filter: From 1f3bfee603532393dfe2f2ac2a6dd022d054e2d5 Mon Sep 17 00:00:00 2001 From: chen Date: Mon, 11 Sep 2023 17:02:14 +0800 Subject: [PATCH 10/22] update docs cookbook part --- docs/03.Advanced-Cookbook/3.01.MQTT-Proxy.md | 19 +- docs/03.Advanced-Cookbook/3.02.Flash-Sale.md | 29 +- .../3.03.Multiple-API-Orchestration.md | 17 +- .../3.04.Canary-Release.md | 23 +- .../3.05.Distributed-Tracing.md | 10 +- .../3.06.Service-Proxy.md | 3 - .../3.06.Service-Registry.md | 137 ++++++++++ docs/03.Advanced-Cookbook/3.07.WasmHost.md | 23 +- docs/03.Advanced-Cookbook/3.08.FaaS.md | 249 ++++++++++++++++- docs/03.Advanced-Cookbook/3.09.Workflow.md | 185 ++++++++++++- docs/03.Advanced-Cookbook/3.10.Performance.md | 191 ++++++++++++- docs/03.Advanced-Cookbook/3.11.Migrate.md | 175 +++++++++++- docs/03.Advanced-Cookbook/README.md | 2 +- docs/07.Reference/7.4.FaaSController.md | 256 ++++++++++++++++++ docs/imgs/architecture.png | Bin 0 -> 44191 bytes docs/imgs/canary-release-case-1.png | Bin 0 -> 7263 bytes docs/imgs/canary-release-case-2.png | Bin 0 -> 8946 bytes docs/imgs/canary-release-case-3.png | Bin 0 -> 11742 bytes docs/imgs/chatgpt-bot-workflow.png | Bin 0 -> 36387 bytes docs/imgs/easegress-cluster-connections.png | Bin 0 -> 21382 bytes docs/imgs/easegress-cluster-nodes.png | Bin 0 -> 17052 bytes docs/imgs/easegress.svg | 18 ++ docs/imgs/readme-httpserver.png | Bin 0 -> 50494 bytes docs/imgs/readme-pipeline.png | Bin 0 -> 83406 bytes docs/imgs/rss-pipeline.png | Bin 0 -> 27745 bytes docs/imgs/stress-test-p99-latency.png | Bin 0 -> 500932 bytes docs/imgs/stress-test-rps.png | Bin 0 -> 668193 bytes docs/imgs/tracing-cloudflare-span.png | Bin 0 -> 71886 bytes .../tracing-cloudflare-transform-header.png | Bin 0 -> 13547 bytes docs/imgs/tracing-exporter.png | Bin 0 -> 118145 bytes docs/imgs/translation-bot-data.png | Bin 0 -> 34001 bytes docs/imgs/translation-bot-workflow.png | Bin 0 -> 53178 bytes 32 files changed, 1273 insertions(+), 64 deletions(-) delete mode 100644 docs/03.Advanced-Cookbook/3.06.Service-Proxy.md create mode 100644 docs/03.Advanced-Cookbook/3.06.Service-Registry.md create mode 100644 docs/07.Reference/7.4.FaaSController.md create mode 100644 docs/imgs/architecture.png create mode 100644 docs/imgs/canary-release-case-1.png create mode 100644 docs/imgs/canary-release-case-2.png create mode 100644 docs/imgs/canary-release-case-3.png create mode 100644 docs/imgs/chatgpt-bot-workflow.png create mode 100644 docs/imgs/easegress-cluster-connections.png create mode 100644 docs/imgs/easegress-cluster-nodes.png create mode 100644 docs/imgs/easegress.svg create mode 100644 docs/imgs/readme-httpserver.png create mode 100644 docs/imgs/readme-pipeline.png create mode 100644 docs/imgs/rss-pipeline.png create mode 100644 docs/imgs/stress-test-p99-latency.png create mode 100644 docs/imgs/stress-test-rps.png create mode 100644 docs/imgs/tracing-cloudflare-span.png create mode 100644 docs/imgs/tracing-cloudflare-transform-header.png create mode 100644 docs/imgs/tracing-exporter.png create mode 100644 docs/imgs/translation-bot-data.png create mode 100644 docs/imgs/translation-bot-workflow.png diff --git a/docs/03.Advanced-Cookbook/3.01.MQTT-Proxy.md b/docs/03.Advanced-Cookbook/3.01.MQTT-Proxy.md index ac7cb3fe06..23ecea2eab 100644 --- a/docs/03.Advanced-Cookbook/3.01.MQTT-Proxy.md +++ b/docs/03.Advanced-Cookbook/3.01.MQTT-Proxy.md @@ -1,6 +1,5 @@ -# MQTT Proxy +# MQTT Proxy -- [MQTT Proxy](#mqtt-proxy) - [Background](#background) - [Design](#design) - [Example](#example) @@ -12,12 +11,12 @@ -# Background +## Background - MQTT is a standard messaging protocol for IoT (Internet of Things) which is extremely lightweight and used by a wide variety of industries. - By supporting MQTT Proxy in Easegress, MQTT clients can produce messages to backend through publish packet pipeline. - We also provide the HTTP endpoint to allow the backend to send messages to MQTT clients. -# Design +## Design - Use `github.com/eclipse/paho.mqtt.golang/packets` to parse MQTT packet. `paho.mqtt.golang` is a MQTT 3.1.1 go client introduced by Eclipse Foundation (who also introduced the most widely used MQTT broker mosquitto). - As a MQTT proxy, we support MQTT clients to `publish` messages to backend through publish packet pipeline. - As `Pipeline` is protocol independent, it can use MQTT filters to do things like user authentication or topic mapping (map MQTT multi-level topic into single topic and key-value headers). @@ -49,7 +48,7 @@ MQTT client <------------ Easegress <------------------------------- Backend Ser ``` By setting `brokerMode` to `true`. MQTTProxy can both send msg to backend and subscribers. Users can also send msg to clients by using HTTP endpoint. -# Example +## Example Save following yaml to file `mqttproxy.yaml` and then run ```bash egctl create -f mqttproxy.yaml @@ -113,7 +112,7 @@ Now, we support following filters for MQTTProxy: - `MQTTClientAuth`: provide username and password checking for MQTT Connect packet. - `KafkaMQTT`: send MQTT Publish message to Kafka backend. By default, `KafkaMQTT` filter will add `clientID`, `username`, `mqttTopic` to Kafka message headers. -# Topic Mapping +## Topic Mapping In MQTT, there are multi-levels in a topic. Topic mapping is used to map MQTT topic to a single topic with headers. For example: ``` MQTT multi-level topics: @@ -190,7 +189,7 @@ filters: headerKey: kafka-headers ``` -## Match different topic mapping policy +### Match different topic mapping policy Consider there may be multiple schemas for your MQTT topic, so we first provide a router to route your MQTT topic to different mapping policies and then do the map in that policy. For example, @@ -211,7 +210,7 @@ schema2: direct/device/status means that we use MQTT topic level 0 to match `matchExpr` to find a corresponding policy. In this case, `gateway/gate123/iphone/log` will match policy `gateway`, `direct/iphone/log` will match policy `direct`. -## Detail of single policy +### Detail of single policy ``` policies: - name: direct @@ -264,7 +263,7 @@ Empty `topicMapper` means there is no map between the MQTT topic and Kafka topic > Note: For MQTT topic `"gateway/gate123/iphone/log"`, index 0 is `"gateway"`. For `"/gateway/gate123/iphone/log"` index 0 is still `"gateway"` not `""`. So, index 0 is the first non-empty level of multi-level MQTT topic. -# HTTP endpoint +## HTTP endpoint We support the backend to send messages back to MQTT clients through the HTTP endpoint. API for http endpoint: @@ -311,6 +310,6 @@ the clients subscribe following topics will receive the message: "+/+/+" ``` -# References +## References 1. https://github.com/eclipse/paho.mqtt.golang 2. http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html diff --git a/docs/03.Advanced-Cookbook/3.02.Flash-Sale.md b/docs/03.Advanced-Cookbook/3.02.Flash-Sale.md index a12f159a5e..d5cf9d5a31 100644 --- a/docs/03.Advanced-Cookbook/3.02.Flash-Sale.md +++ b/docs/03.Advanced-Cookbook/3.02.Flash-Sale.md @@ -1,18 +1,17 @@ -# Handle Flash Sale With Easegress and WebAssembly - -- [Handle Flash Sale With Easegress and WebAssembly](#handle-flash-sale-with-easegress-and-webassembly) - - [1. Getting Started](#1-getting-started) - - [1.1 Setting Up The Flash Sale Project](#11-setting-up-the-flash-sale-project) - - [1.2 Creating HTTP Server And Pipeline In Easegress](#12-creating-http-server-and-pipeline-in-easegress) - - [1.3 Verify What We Have Done](#13-verify-what-we-have-done) - - [2. Block All Requests Before Flash Sale Start](#2-block-all-requests-before-flash-sale-start) - - [3. Randomly Block Requests](#3-randomly-block-requests) - - [4. Lucky Once, Lucky Always](#4-lucky-once-lucky-always) - - [5. Limit The Number Of Permitted Users](#5-limit-the-number-of-permitted-users) - - [6. Reuse The Program](#6-reuse-the-program) - - [6.1 Parameters](#61-parameters) - - [6.2 Manage Shared Data](#62-manage-shared-data) - - [7. Summary](#7-summary) +# Handle Flash Sale With Easegress and WebAssembly + +- [1. Getting Started](#1-getting-started) + - [1.1 Setting Up The Flash Sale Project](#11-setting-up-the-flash-sale-project) + - [1.2 Creating HTTP Server And Pipeline In Easegress](#12-creating-http-server-and-pipeline-in-easegress) + - [1.3 Verify What We Have Done](#13-verify-what-we-have-done) +- [2. Block All Requests Before Flash Sale Start](#2-block-all-requests-before-flash-sale-start) +- [3. Randomly Block Requests](#3-randomly-block-requests) +- [4. Lucky Once, Lucky Always](#4-lucky-once-lucky-always) +- [5. Limit The Number Of Permitted Users](#5-limit-the-number-of-permitted-users) +- [6. Reuse The Program](#6-reuse-the-program) + - [6.1 Parameters](#61-parameters) + - [6.2 Manage Shared Data](#62-manage-shared-data) +- [7. Summary](#7-summary) A flash sale is a discount or promotion offered by an eCommerce store for a short period. The quantity is limited, which often means the discounts are higher or more significant than run-of-the-mill promotions. diff --git a/docs/03.Advanced-Cookbook/3.03.Multiple-API-Orchestration.md b/docs/03.Advanced-Cookbook/3.03.Multiple-API-Orchestration.md index 8b67bb845e..0d79f3ca34 100644 --- a/docs/03.Advanced-Cookbook/3.03.Multiple-API-Orchestration.md +++ b/docs/03.Advanced-Cookbook/3.03.Multiple-API-Orchestration.md @@ -1,9 +1,24 @@ -# Build A Telegram Translation Bot With Easegress +# Build A Telegram Translation Bot With Easegress Easegress is the next-generation traffic-based gateway product developed by MegaEase. It is completely architected on top of cloud-native technology, avoiding the shortcomings of traditional reverse proxy in terms of high availability, traffic orchestration, monitoring, service discovery, etc. We released Easegress v2.0 recently, with another significant enhancement to traffic orchestration, allowing users to implement a super API by orchestrating multiple APIs without writing any code. This article will demonstrate this feature by building a Telegram translation bot. This bot can automatically translate incoming messages into Chinese, Japanese, and English, and, in addition to text messages, it also supports translating voice and photo messages. +- [1. Prerequisites](#1-prerequisites) +- [2. How It Works](#2-how-it-works) +- [3. Pipeline](#3-pipeline) +- [4. Filter](#4-filter) + - [4.1 Backend Proxies](#41-backend-proxies) + - [4.2 Detect Message Type](#42-detect-message-type) + - [4.3 Reading The File Content](#43-reading-the-file-content) + - [4.4 Speech Recognize and OCR](#44-speech-recognize-and-ocr) + - [4.5 Text Extraction](#45-text-extraction) + - [4.6 Translating](#46-translating) + - [4.7 Construct Translation Results Into Reply Message](#47-construct-translation-results-into-reply-message) + - [4.8 Response](#48-response) +- [5. Deploy](#5-deploy) + + ## 1. Prerequisites Since the bot needs to receive Telegram message notifications and call third-party APIs, we must prepare the following in advance: diff --git a/docs/03.Advanced-Cookbook/3.04.Canary-Release.md b/docs/03.Advanced-Cookbook/3.04.Canary-Release.md index 4dd54a06b9..904ce2947d 100644 --- a/docs/03.Advanced-Cookbook/3.04.Canary-Release.md +++ b/docs/03.Advanced-Cookbook/3.04.Canary-Release.md @@ -1,13 +1,12 @@ -# Canary Release With Cloudflare +# Canary Release With Cloudflare -- [Canary Release With Cloudflare](#canary-release-with-cloudflare) - - [Background](#background) - - [Advantages](#advantages) - - [Key point](#key-point) - - [Why use Easegress and Cloudflare](#why-use-easegress-and-cloudflare) - - [based on geographic location](#based-on-geographic-location) - - [based on user devices](#based-on-user-devices) - - [based on user OS](#based-on-user-os) +- [Background](#background) + - [Advantages](#advantages) + - [Key point](#key-point) +- [Why use Easegress and Cloudflare](#why-use-easegress-and-cloudflare) +- [Based on geographic location](#based-on-geographic-location) +- [Based on user devices](#based-on-user-devices) +- [Based on user OS](#based-on-user-os) ## Background @@ -33,7 +32,7 @@ A [canary release](https://martinfowler.com/bliki/CanaryRelease.html) is a softw - For traffic scheduling, Easegress can be used to route traffic to different canary releases. -## based on geographic location +## Based on geographic location ![case 1](../imgs/canary-release-case-1.png) @@ -109,7 +108,7 @@ curl http://127.0.0.1:8888/order -H "cf-ipcity: Shanghai" {"order_id":"5245000","role_id":"44312","order_status":1,"order_version":"v1"} ``` -## based on user devices +## Based on user devices ![case 2](../imgs/canary-release-case-2.png) @@ -195,7 +194,7 @@ curl http://127.0.0.1:10080/ecommerce -H "user-agent:Mozilla/5.0 (Macintosh; Int {"order_id":"5245000","role_id":"44312","order_status":1,"order_version":"v2","notify_status":1,"notify_version":"v2"} ``` -## based on user OS +## Based on user OS ![case 3](../imgs/canary-release-case-3.png) diff --git a/docs/03.Advanced-Cookbook/3.05.Distributed-Tracing.md b/docs/03.Advanced-Cookbook/3.05.Distributed-Tracing.md index c0ce3fcb72..c24e8cc516 100644 --- a/docs/03.Advanced-Cookbook/3.05.Distributed-Tracing.md +++ b/docs/03.Advanced-Cookbook/3.05.Distributed-Tracing.md @@ -1,4 +1,10 @@ -# Distributed Tracing +# Distributed Tracing + +- [Custom attributes](#custom-attributes) +- [Exporter](#exporter) +- [Backward Compatibility](#backward-compatibility) +- [Usage with Cloudflare](#usage-with-cloudflare) + Easegress tracing is based on [OpenTelemetry](https://opentelemetry.io/). We can enable tracing in Traffic Gates, for example, in `HTTPServer`, we can do this by defining the `tracing` entry. Tracing creates spans containing the tracing service name (`tracing.serviceName`) and other information. The matched pipeline will start a child span, and its internal filters will start children spans according to their implementation and configuration. For example, the `Proxy` filter has a specific span implementation. @@ -42,7 +48,7 @@ rules: ## Exporter -In the above example, we can see that we are sending span to Zipkin, thanks to the standardization of OpenTelemetry, we can also send span to other tracing backend, which currently supports Jaeger, Zipkin, OTLP (OpenTelemetry Collector), you can refer to [tracing](../reference/controllers.md#tracingspec) for details. +In the above example, we can see that we are sending span to Zipkin, thanks to the standardization of OpenTelemetry, we can also send span to other tracing backend, which currently supports Jaeger, Zipkin, OTLP (OpenTelemetry Collector), you can refer to [tracing](../07.Reference/7.1.Controllers.md#tracingspec) for details. ![exporter](../imgs/tracing-exporter.png) diff --git a/docs/03.Advanced-Cookbook/3.06.Service-Proxy.md b/docs/03.Advanced-Cookbook/3.06.Service-Proxy.md deleted file mode 100644 index 862427c5fe..0000000000 --- a/docs/03.Advanced-Cookbook/3.06.Service-Proxy.md +++ /dev/null @@ -1,3 +0,0 @@ -TODO: - -from from serverproxy.md diff --git a/docs/03.Advanced-Cookbook/3.06.Service-Registry.md b/docs/03.Advanced-Cookbook/3.06.Service-Registry.md new file mode 100644 index 0000000000..59d0caa4dc --- /dev/null +++ b/docs/03.Advanced-Cookbook/3.06.Service-Registry.md @@ -0,0 +1,137 @@ +# Service Registry + +- [Basic: Load Balance](#basic-load-balance) +- [Dynamic: Integration with Service Registry](#dynamic-integration-with-service-registry) + - [Zookeeper](#zookeeper) + - [Consul](#consul) + - [Eureka](#eureka) + +Easegress servers as a reverse proxy. It can easily integrate with mainstream Service Registries. + + +## Basic: Load Balance + +```yaml +name: pipeline-reverse-proxy +kind: Pipeline +flow: + - filter: proxy +filters: + - name: proxy + kind: Proxy + pools: + - servers: + - url: http://127.0.0.1:9095 + - url: http://127.0.0.1:9096 + - url: http://127.0.0.1:9097 + loadBalance: + policy: roundRobin +``` + + +## Dynamic: Integration with Service Registry + +We integrate `Proxy` with service registries such as [Consul](https://github.com/megaease/easegress/blob/main/doc/controllers.md#consulserviceregistry), [Etcd](https://github.com/megaease/easegress/blob/main/doc/controllers.md#etcdserviceregistry), [Zookeeper](https://github.com/megaease/easegress/blob/main/doc/controllers.md#zookeeperserviceregistry), [Eureka](https://github.com/megaease/easegress/blob/main/doc/controllers.md#eurekaserviceregistry). You need to create one of them to connect the external service registry. The service registry config takes higher priority than static servers. If the dynamic servers pulling failed, it will use static servers if there are. + + +### Zookeeper + +1. First we need to create a ZookeeperServiceRegistry in Easegress + +``` yaml +kind: ZookeeperServiceRegistry +name: zookeeper-001 +zkservices: [127.0.0.1:2181] +prefix: /services +conntimeout: 6s +syncInterval: 10s +``` + +2. Create a pipeline and set its `serviceRegistry` field into `zookeeper-001` and it will look up the zookeeper configuration for the service named `springboot-application-order` as in field `serviceName`. + +```yaml +name: pipeline-reverse-proxy +kind: Pipeline +flow: + - filter: proxy +filters: + - name: proxy + kind: Proxy + pools: + - servers: + - url: http://127.0.0.1:9095 + - url: http://127.0.0.1:9096 + - url: http://127.0.0.1:9097 + serviceRegistry: zookeeper-001 # + + serviceName: springboot-application-order # + + loadBalance: + policy: roundRobin +``` + + +### Consul + +1. First we need to create a ConsulServiceRegistry in Easegress + +```yaml +kind: ConsulServiceRegistry +name: consul-001 +address: '127.0.0.1:8500' +scheme: http +syncInterval: 10s +``` + +2. Create a pipeline and set its `serviceRegistry` field into `consul-001` and it will look up the consul configuration for the service named `springboot-application-order` as in field `serviceName`. + +```yaml +name: pipeline-reverse-proxy +kind: Pipeline +flow: + - filter: proxy +filters: + - name: proxy + kind: Proxy + pools: + - servers: + - url: http://127.0.0.1:9095 + - url: http://127.0.0.1:9096 + - url: http://127.0.0.1:9097 + serviceRegistry: consul-001 # + + serviceName: springboot-application-order # + + loadBalance: + policy: roundRobin +``` + + +### Eureka +1. First we need to create a EurekaServiceRegistry in Easegress + +```yaml +kind: EurekaServiceRegistry +name: eureka-001 +endpoints: ['http://127.0.0.1:8761/eureka'] +syncInterval: 10s +``` + + +2. Create a pipeline and set its `serviceRegistry` field into `eureka-001` and it will look up the eureka configuration for the service named `springboot-application-order` as in field `serviceName`. + +```yaml +name: pipeline-reverse-proxy +kind: Pipeline +flow: + - filter: proxy +filters: + - name: proxy + kind: Proxy + pools: + - servers: + - url: http://127.0.0.1:9095 + - url: http://127.0.0.1:9096 + - url: http://127.0.0.1:9097 + serviceRegistry: eureka-001 # + + serviceName: springboot-application-order # + + loadBalance: + policy: roundRobin +``` + diff --git a/docs/03.Advanced-Cookbook/3.07.WasmHost.md b/docs/03.Advanced-Cookbook/3.07.WasmHost.md index 727406bb2c..d2cbef48a5 100644 --- a/docs/03.Advanced-Cookbook/3.07.WasmHost.md +++ b/docs/03.Advanced-Cookbook/3.07.WasmHost.md @@ -1,15 +1,14 @@ -# WasmHost - -- [WasmHost](#wasmhost) - - [Why Use WasmHost](#why-use-wasmhost) - - [Examples](#examples) - - [Basic: Noop](#basic-noop) - - [Add a New Header](#add-a-new-header) - - [Add a New Header According to Configuration](#add-a-new-header-according-to-configuration) - - [Add a Cookie](#add-a-cookie) - - [Mock Response](#mock-response) - - [Access Shared Data](#access-shared-data) - - [Return a Result Other Than 0](#return-a-result-other-than-0) +# WasmHost + +- [Why Use WasmHost](#why-use-wasmhost) +- [Examples](#examples) + - [Basic: Noop](#basic-noop) + - [Add a New Header](#add-a-new-header) + - [Add a New Header According to Configuration](#add-a-new-header-according-to-configuration) + - [Add a Cookie](#add-a-cookie) + - [Mock Response](#mock-response) + - [Access Shared Data](#access-shared-data) + - [Return a Result Other Than 0](#return-a-result-other-than-0) The `WasmHost` is a filter of Easegress which can be orchestrated into a pipeline. But while the behavior of all other filters are defined by filter developers and can only be fine-tuned by configuration, this filter implements a host environment for user-developed [WebAssembly](https://webassembly.org/) code, which enables users to control the filter behavior completely. diff --git a/docs/03.Advanced-Cookbook/3.08.FaaS.md b/docs/03.Advanced-Cookbook/3.08.FaaS.md index 1f149c22b7..dbe03c214e 100644 --- a/docs/03.Advanced-Cookbook/3.08.FaaS.md +++ b/docs/03.Advanced-Cookbook/3.08.FaaS.md @@ -1,3 +1,248 @@ -TODO: +# FaaS -from faas.md \ No newline at end of file +- [Background](#background) +- [Easegress works with FaaS](#easegress-works-with-faas) +- [Examples](#examples) + - [Scenario 1: Run a FaaS function beside Easegress](#scenario-1-run-a-faas-function-beside-easegress) + - [Scenario 2: Limit FaaS function resources using](#scenario-2-limit-faas-function-resources-using) + - [Scenario 3: Longlife FaaS function](#scenario-3-longlife-faas-function) + - [Scenario 4: Autoscaling FaaS Function according to rps](#scenario-4-autoscaling-faas-function-according-to-rps) +- [References](#references) + - [Resource limiter](#resource-limiter) + - [Long life function](#long-life-function) + - [RPS autoscaling](#rps-autoscaling) + +## Background + +* Function As Services is a recently popular cloud computing solution. It provides a platform for users to develop, run and manage application functionalities without the complexity of building and maintaining the infrastructure.[1] +* Easegress provides a business controller for implementing these zero-infrastructure-maintaining and auto-scaling requirements. + +## Easegress works with FaaS + +* Isolation: separate Control logic and Business logic +* Traffic originated: Original near traffic, easier to integrate +* Resource saving: reusing Easegress+K8s machine resources. +* Pay what you used: reducing small customize features' developing and maintenance cost. + +## Examples + + +### Scenario 1: Run a FaaS function beside Easegress + +* After implementing your business logic and having Knative installed to your Kubernetes cluster. You can refer to [FaaSController](../07.Reference/7.4.FaaSController.md) for more info + +1. Create a FaaSController[2] + +``` bash +echo 'name: faascontroller +kind: FaaSController +provider: knative # FaaS provider kind, currently we only support Knative +syncInterval: 10s + +httpServer: + http3: false + port: 10083 + keepAlive: true + keepAliveTimeout: 60s + maxConnections: 10240 + +knative: + networkLayerURL: http://${knative_kourier_clusterIP} + hostSuffix: example.com ' | egctl create -f - +``` + +2. Deploy a function into Easegress and Knative, prepare a YAML content as below: + +``` yaml +name: "demo" +image: "gcr.io/knative-samples/helloworld-go" # you can change this to any pullable image +port: 8080 # image exposes port 8080 +autoScaleType: "concurrency" +autoScaleValue: "100" +minReplica: 0 +maxReplica: 1 +limitCPU: "1000m" +limitMemory: "1000Mi" +requestCPU: "80m" +requestMemory: "20Mi" +requestAdaptor: + header: + set: + X-Func: demo +``` + +* Save it into `/home/easegress/function.yaml`, using command to deploy it in Easegress: + +``` bash +$ curl --data-binary @/home/easegress/function.yaml -X POST -H 'Content-Type: text/vnd.yaml' http://127.0.0.1:2381/apis/v2/faas/faascontroller +``` +**Note** this command should be run in Easegress' instance environment and 2381 is the default admin traffic port. If your Easegress instance uses different port, please change 2381 to the correct port. + + +1. Get the function's status, make sure it is in `active` status + +``` bash +$ curl http://127.0.0.1:2381/apis/v2/faas/faascontroller/demo +spec: + name: demo + image: gcr.io/knative-samples/helloworld-go + port: 8080 + autoScaleType: concurrency + autoScaleValue: "100" + minReplica: 0 + maxReplica: 1 + limitCPU: 1000m + limitMemory: 1000Mi + requestCPU: 80m + requestMemory: 20Mi + requestAdaptor: + host: "" + method: "" + header: + del: [] + set: + X-Func: demo + add: {} + body: "" + compress: "" + decompress: "" +status: + name: demo + state: active + event: ready + extData: {} +fsm: null +``` + +4. Request the FaaS function by Easegress HTTP traffic gate with `X-FaaS-Func-Name: demo` in the HTTP header. +**Note**: this example's container image `gcr.io/knative-samples/helloworld-go` serves on path `/`. For different function images, the path might differ. + +``` bash +$ curl http://127.0.0.1:10083 -H "X-FaaS-Func-Name: demo" +Hello World! +``` + +### Scenario 2: Limit FaaS function resources using +* You want to make sure at the maximum instance number can only be under 50, and it can only "180m" CPU and "100Mi" memory usage maximum allowed per instance. To providing meaningful resources amount for the function, you also want to make sure one instance has at least a "100m" CPU and "50mi" memory provision. (The CPU and memory limitation usage value comes from Kubernetes resource). + +``` yaml +name: demo +#... + +limitedMemory: "200Mi" +limitedCPU: "180m" +requireMemory: "100Mi" +requireCPU: "100m" +minReplica: 0 +maxReplica: 50 + +``` + +* For the full YAML, see [here](#resource-limiter) + +* Add the configuration above in #Scenario 1's `/home/easegress/function.yaml` + +1. Stop the function execution by using command + +``` bash +$ curl http://127.0.0.1:2381/apis/v2/faas/faascontroller/demo/stop -X PUT +``` + +* The function will become `inactive` then we can update the resource limitation safely. + +2. Update the function's spec + +``` bash +$ curl --data-binary @/home/easegress/function.yaml -X PUT -H 'Content-Type: text/vnd.yaml' http://127.0.0.1:2381/apis/v2/faas/faascontroller/demo +``` + +3. Verify the update +* Waiting for the function starts successfully and becomes `active` +* Request the function with step4 in Scenario 1. + +### Scenario 3: Longlife FaaS function +* In the same special cases, you may want your FaaS function to have at least one instance running beside Easegress. + +``` yaml +name: demo +#... +minReplica: 1 +#... +``` + +* For the full YAML, see [here](#long-life-function) + +1. Modifying the `minReplica` above in #Scenario 1's `/home/easegress/function.yaml` + +2. Update the function spec and verify it as in Scenario 2's steps 2 - 3. + +### Scenario 4: Autoscaling FaaS Function according to rps + +* If you don't need to control the function's allowed request precisely, `RPS` based autoscaling is a good choice. + +``` yaml +name: demo +#... +autoScaleType: "rps" +autoScaleValue: "6000" +#... +``` + +* For the full YAML, see [here](#rps-autoscaling) + +1. Modifying the `autoScaleType` and `autoScaleValue" above in #Scenario 1's `/home/easegress/function.yaml` + +2. Update the function spec and verify it as in Scenario 2's step 2 - 3. + +## References + +1. https://en.wikipedia.org/wiki/Function_as_a_service +2. https://github.com/megaease/easegress/blob/main/doc/faascontroller.md + +### Resource limiter + +```yaml +name: demo +image: "${image_url}" +port: 8089 +autoScaleType: "concurrency" +autoScaleValue: "100 +limitedMemory: "200Mi" +limitedCPU: "180m" +requireMemory: "100Mi" +requireCPU: "100m" +minReplica: 0 +maxReplica: 50 +``` + +### Long life function + +``` yaml +name: demo +image: "${image_url}" +port: 8089 +autoScaleType: "concurrency" +autoScaleValue: "100" +limitedMemory: "200Mi" +limitedCPU: "180m" +requireMemory: "100Mi" +requireCPU: "100m" +minReplica: 1 +maxReplica: 50 +``` + +### RPS autoscaling + +``` yaml +name: demo +image: "${image_url}" +port: 8089 +autoScaleType: "rps" +autoScaleValue: "6000" +limitedMemory: "200Mi" +limitedCPU: "180m" +requireMemory: "100Mi" +requireCPU: "100m" +minReplica: 0 +maxReplica: 50 +``` diff --git a/docs/03.Advanced-Cookbook/3.09.Workflow.md b/docs/03.Advanced-Cookbook/3.09.Workflow.md index 023fe41810..c1101bcc82 100644 --- a/docs/03.Advanced-Cookbook/3.09.Workflow.md +++ b/docs/03.Advanced-Cookbook/3.09.Workflow.md @@ -1,3 +1,184 @@ -TODO +# Workflow -from workflow.md \ No newline at end of file +- [Background](#background) +- [Example](#example) + - [Step 1. Create a Slack Webhook](#step-1-create-a-slack-webhook) + - [Step 2. Create a pipeline for the workflow](#step-2-create-a-pipeline-for-the-workflow) + - [Step 3: Create an HTTPServer to receive client request](#step-3-create-an-httpserver-to-receive-client-request) + - [Step 4: See the result](#step-4-see-the-result) +- [References](#references) + +## Background + +A workflow consists of an orchestrated and repeatable pattern of activity, enabled by the systematic organization of resources into processes that transform materials, provide services, or process information. It can be depicted as a sequence of operations, the work of a person or group, the work of an organization of staff, or one or more simple or complex mechanisms.[1] + +## Example + +Read an RSS feed, build the article list into a Slack message, and then send it to Slack. + +### Step 1. Create a Slack Webhook + +Please follow [this document](https://api.slack.com/messaging/webhooks) to create a new Slack WebHook, the URL of the Webhook will be like `https://hooks.slack.com/services/T0XXXXXXX/B0YYYYYYYYY/ZZZZZZZZZZZZZZZZZZZZZZZZ`. + +### Step 2. Create a pipeline for the workflow + +Save the below YAML to `rss-pipeline.yaml`, and make sure you have replaced the Slack Webhook URL with yours. + +```yaml +name: rss-pipeline +kind: Pipeline + +flow: +# validate the request, a valid request must contain the 'X-Rss-Url' header, and its value must be a URL. +- filter: validator + +# create the request for the RSS feed. +- filter: buildRssRequest + namespace: rss + +# read the RSS feed, a 3rd party website is used to covert the feed from XML to JSON. +- filter: sendRssRequest + namespace: rss + +# the RSS feed is gzipped, we need to decompress it. +- filter: decompressResponse + namespace: rss + +# create the request to Slack (build the Slack message). +- filter: buildSlackRequest + namespace: slack + +# send the message to Slack. +- filter: sendSlackRequest + namespace: slack + +# build the response for the client. +- filter: buildResponse + +filters: +- name: validator + kind: Validator + headers: + "X-Rss-Url": + regexp: ^https?://.+$ + +- name: buildRssRequest + kind: RequestBuilder + template: | + url: /developers/feed2json/convert?url={{index (index .requests.DEFAULT.Header "X-Rss-Url") 0 | urlquery}} + +- name: sendRssRequest + kind: Proxy + pools: + - loadBalance: + policy: roundRobin + servers: + - url: https://www.toptal.com + compression: + minLength: 4096 + +- name: buildSlackRequest + kind: RequestBuilder + template: | + method: POST + url: /services/T0XXXXXXXXX/B0YYYYYYY/ZZZZZZZZZZZZZZZZZZZZ # This the Slack webhook address, please change it to your own. + body: | + { + "text": "Recent posts - {{.responses.rss.JSONBody.title}}", + "blocks": [{ + "type": "section", + "text": { + "type": "plain_text", + "text": "Recent posts - {{.responses.rss.JSONBody.title}}" + } + }, { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "{{range $index, $item := .responses.rss.JSONBody.items}}• <{{$item.url}}|{{$item.title}}>\n{{end}}" + }}] + } + +- name: sendSlackRequest + kind: Proxy + pools: + - loadBalance: + policy: roundRobin + servers: + - url: https://hooks.slack.com + compression: + minLength: 4096 + + +- name: decompressResponse + kind: ResponseAdaptor + decompress: gzip + + +- name: buildResponse + kind: ResponseBuilder + template: | + statusCode: {{.responses.slack.StatusCode}} + body: {{if eq .responses.slack.StatusCode 200}}RSS feed has been sent to Slack successfully.{{else}}Failed to send the RSS feed to Slack{{end}} +``` + +Then create the RSS pipeline with the command: + +```shell +egctl create -f rss-pipeline.yaml +``` + +### Step 3: Create an HTTPServer to receive client request + +Save below YAML to `http-server.yaml`. + +```yaml +kind: HTTPServer +name: http-server-example +port: 8080 +https: false +keepAlive: true +keepAliveTimeout: 75s +maxConnection: 10240 +cacheSize: 0 +rules: + - paths: + - pathPrefix: /rss + backend: rss-pipeline +``` + +Then create the HTTP server with command: + +```shell +egctl create -f http-server.yaml +``` + +### Step 4: See the result + +Execute the below command and your Slack will receive the article list if everything is correct. +You may use another RSS feed, but please note the maximum message size Slack allowed is about 3K, so you will need to limit the number of articles returned by the RSS feed of some sites(e.g. Hack News) + +```shell +$ curl -v http://127.0.0.1:8080/rss -H X-Rss-Url:https://www.coolshell.cn/rss +* Trying 127.0.0.1:8080... +* TCP_NODELAY set +* Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0) +> GET /rss HTTP/1.1 +> Host: 127.0.0.1:8080 +> User-Agent: curl/7.68.0 +> Accept: */* +> X-Rss-Url:https://www.coolshell.cn/rss +> +* Mark bundle as not supporting multiuse +< HTTP/1.1 200 OK +< Date: Fri, 17 Jun 2022 08:29:58 GMT +< Content-Length: 45 +< Content-Type: text/plain; charset=utf-8 +< +* Connection #0 to host 127.0.0.1 left intact +RSS feed has been sent to Slack successfully. +``` + +## References + +1. https://en.wikipedia.org/wiki/Workflow diff --git a/docs/03.Advanced-Cookbook/3.10.Performance.md b/docs/03.Advanced-Cookbook/3.10.Performance.md index 518137e6e4..85c05987bc 100644 --- a/docs/03.Advanced-Cookbook/3.10.Performance.md +++ b/docs/03.Advanced-Cookbook/3.10.Performance.md @@ -1,3 +1,190 @@ -TODO: +# Performance + +- [Basic: Load Balance](#basic-load-balance) +- [Performance: Compression and Caching](#performance-compression-and-caching) + - [Compression in filter `Proxy`](#compression-in-filter-proxy) + - [Caching in filter `Proxy`](#caching-in-filter-proxy) + - [Caching in `HTTPServer`](#caching-in-httpserver) +- [References](#references) + - [Proxy Compression](#proxy-compression) + - [Proxy Caching](#proxy-caching) + - [HTTPServer route rule caching](#httpserver-route-rule-caching) -from performance.md \ No newline at end of file +Easegress supports compression and caching in Pipeline and HTTPServer for performance improvement as a reverse proxy. + + +## Basic: Load Balance + +```yaml +name: pipeline-reverse-proxy +kind: Pipeline +flow: + - filter: proxy +filters: + - name: proxy + kind: Proxy + pools: + - servers: + - url: http://127.0.0.1:9095 + - url: http://127.0.0.1:9096 + - url: http://127.0.0.1:9097 + loadBalance: + policy: roundRobin +``` + +## Performance: Compression and Caching + +### Compression in filter `Proxy` + +* Easegress proxy filter supports `gzip` type compression. It can save the bandwidth between the client and Easegress and reduce the time cost. + +```yaml +name: pipeline-reverse-proxy +kind: Pipeline +flow: + - filter: proxy +filters: + - name: proxy + kind: Proxy +#... + compression: + minLength: 1024 +#... +``` + +* As the example above, we only need to value the `minLength` field to tell Easegress' proxy filter compressing response body whose size is bigger than `minLength`. Also, it will add the `gzip` header automatically if it's truly compressed. + +* For the full yaml, see [here](#proxy-compression) + +### Caching in filter `Proxy` + +* Easegress proxy filter has a `pool` section for describing the traffic forwarding backends. And it also supports caching the response according to the HTTP Methods and the HTTP response code. **Recommend enabling this feature only in the routing static resources scenario**. + +``` yaml +name: pipeline-reverse-proxy +kind: Pipeline +flow: + - filter: proxy +filters: + - name: proxy + kind: Proxy + pools: +#... + memoryCache: + expiration: 10s + maxEntryBytes: 4096 + codes: + - 200 + - 201 + methods: + - GET + - HEAD +#... +``` + +* The example above will cache the response which size is smaller than 4096, and the response code is 200 or 201, with the HTTP methods Get and Head. + +* For the full YAML, see [here](#proxy-caching) + +### Caching in `HTTPServer` + +* As a traffic gate of Easegress, HTTPServer also supports caching routing rules. It reduces the HTTPServer route searching cost. HTTPServer will use the request's Host, Method, and Path to form a key for the build-in LRU rule cache. + +```yaml +kind: HTTPServer +name: http-server-example +port: 10080 +https: false +#... +cacheSize: 10240 +#... +``` + +* As the example above, all we need is to set the `cacheSize` to indicated the LRU cache's size. It will disuse the least used cache rules firstly. + +* For the full YAML, see [here](#httpserver-route-rule-caching) + +## References +### Proxy Compression + +```yaml +name: pipeline-reverse-proxy +kind: Pipeline +flow: + - filter: proxy +filters: + - name: proxy + kind: Proxy + pools: + - servers: + - url: http://127.0.0.1:9095 + - url: http://127.0.0.1:9096 + - url: http://127.0.0.1:9097 + loadBalance: + policy: roundRobin + compression: + minLength: 1024 +``` + +### Proxy Caching + +```yaml +name: pipeline-reverse-proxy +kind: Pipeline +flow: + - filter: proxy +filters: + - name: proxy + kind: Proxy + pools: + - servers: + - url: http://127.0.0.1:9095 + - url: http://127.0.0.1:9096 + - url: http://127.0.0.1:9097 + loadBalance: + policy: roundRobin + memoryCache: + expiration: 10s + maxEntryBytes: 4096 + codes: + - 200 + - 201 + methods: + - GET + - HEAD +``` + +### HTTPServer route rule caching + +``` yaml +kind: HTTPServer +name: http-server-example +port: 10080 +https: false +http3: false +keyBase64: +certBase64: +keepAlive: true +keepAliveTimeout: 75s +maxConnection: 10240 +cacheSize: 10240 +rules: + - paths: + - pathPrefix: /pipeline + backend: http-pipeline-example + - pathPrefix: /remote + backend: remote-pipeline + headers: + - key: X-Activity-No + values: [ "123456", "124456" ] + backend: remote-pipeline1 + - key: X-Activity-No + values: [ "224456" ] + regexp: ^224.*$ + backend: remote-pipeline2 + - key: X-Activity-No + regexp: ^324.*$ + backend: remote-pipeline2 + - path: /func + backend: func-mirror +``` \ No newline at end of file diff --git a/docs/03.Advanced-Cookbook/3.11.Migrate.md b/docs/03.Advanced-Cookbook/3.11.Migrate.md index 3074a2a26e..7be92861ba 100644 --- a/docs/03.Advanced-Cookbook/3.11.Migrate.md +++ b/docs/03.Advanced-Cookbook/3.11.Migrate.md @@ -1,3 +1,174 @@ -TODO +# Migrate v1.x Filter To v2.0 -from migration.md \ No newline at end of file +Easegress v2.0 introduces exciting new features like protocol-independent +pipeline, multiple requests/responses support, etc. But this also makes +it incompatible with v1.x. We need to do some code modifications to bring +filters designed for v1.x to v2.0. + +This document is a guide on how to do the migration, we will use the `Mock` +filter as an example. + +- [1. Define a kind for the filter](#1-define-a-kind-for-the-filter) +- [2. Remove legacy functions](#2-remove-legacy-functions) +- [3. Update Filter and its Spec definition](#3-update-filter-and-its-spec-definition) +- [4. Add new function `Name`](#4-add-new-function-name) +- [5. Update the `Kind` function](#5-update-the-kind-function) +- [6. Update the `Init` and `Inherit` function](#6-update-the-init-and-inherit-function) +- [7. Update the `Handle` function](#7-update-the-handle-function) +- [8. Remove `httppipeline` From Imported Packages](#8-remove-httppipeline-from-imported-packages) +- [9. Update Other Code](#9-update-other-code) + + +## 1. Define a kind for the filter + +```go +var kind = &filters.Kind{ + Name: Kind, + Description: "Mock mocks the response.", + Results: []string{resultMocked}, + DefaultSpec: func() filters.Spec { + return &Spec{} + }, + CreateInstance: func(spec filters.Spec) filters.Filter { + return &Mock{spec: spec.(*Spec)} + }, +} +``` + +Note the value of the `Description` field is from the existing `Description` +function: + +```go +func (m *Mock) Description() string { + return "Mock mocks the response." +} +``` + +`Results` is from the existing `Results` function: + +```go +var results = []string{resultMocked} + +func (m *Mock) Results() []string { + return results +} +``` + +The body `DefaultSpec` is just the same as the existing `DefaultSpec` function. + +For `CreateInstance`, in most cases, we only need to return a new instance +of the filter, but please remember to set the `spec` field before returning it. + +## 2. Remove legacy functions + +The legacy functions, `Description`, `Results` and `DefaultSpec` function are +now useless, remove them. + +## 3. Update Filter and its Spec definition + +First, add `BaseSpec` to the `Spec` definition: + +```go +Spec struct { + filters.BaseSpec `yaml:",inline"` // add this line + Rules []*Rule `yaml:"rules"` +} +``` + +Then, remove the `filterSpec` field from the filter definition: + +```go +type Mock struct { + filterSpec *httppipeline.FilterSpec // remove this line + spec *Spec +} +``` + +## 4. Add new function `Name` + +```go +// Name returns the name of the Mock filter instance. +func (m *Mock) Name() string { + return m.spec.Name() +} +``` + +## 5. Update the `Kind` function + +In v1.x, The function returns the kind name, while in v2.x, it should return +the `kind` object we defined in step 1. + +```go +// Kind returns the kind of Mock. +func (m *Mock) Kind() *filters.Kind { + return kind +} +``` + +## 6. Update the `Init` and `Inherit` function + +Change the prototype of these two functions to: + +```go +func (m *Mock) Init() +``` + +and + +```go +func (m *Mock) Inherit(previousGeneration filters.Filter) +``` + +The spec of the filter has already been assigned in step 1, so related code +can be removed from the two functions. And if you need to access the spec +from the functions, please use `m.spec` directly. + +Note, in Easegress v2, the `previousGeneration` should NOT be closed in +`Inherit` any more, that's `previousGeneration.Close()` should be removed +from `Inherit`. + +## 7. Update the `Handle` function + +First change the prototype to below, note the type of the `ctx` parameter +has changed from `context.HTTPContext` to `*context.Context`: + +```go +func (m *Mock) Handle(ctx *context.Context) string +``` + +Then, because Easegress is not using the chain of responsibility pattern to +call filters any more, we need to change the `return` statements from: + +```go +return ctx.CallNextHandler(result) +``` + +to + +```go +return result +``` + +## 8. Remove `httppipeline` From Imported Packages + +```go +import ( + // ... + "github.com/megaease/easegress/v2/pkg/object/httppipeline" // remove this line + // ... +) + +``` + +## 9. Update Other Code + +`ctx.Request()` need to be updated to `ctx.GetInputRequest()`, +`ctx.GetOutputRequest()`, `ctx.SetInputRequest()` or `ctx.SetOutputRequest()`, +same for `ctx.Response()`. And please make a type assertion if you need a +protocol specific request/response, like +`ctx.GetInputRequest().(*httpprot.Request) if an HTTP request is desired. + +The above is all the general steps to migrate a filter from v1.x to v2.x, +and you may need more works that are specific to your implementation to +complete the migration. We think most of the works would be trivial, but +please feel free to contact us if you need help. \ No newline at end of file diff --git a/docs/03.Advanced-Cookbook/README.md b/docs/03.Advanced-Cookbook/README.md index 06a68501dc..c2ef83e5e7 100644 --- a/docs/03.Advanced-Cookbook/README.md +++ b/docs/03.Advanced-Cookbook/README.md @@ -5,7 +5,7 @@ - [Multiple API Orchestration](3.03.Multiple-API-Orchestration.md) - [Canary Release](3.04.Canary-Release.md) - [Distributed Tracing](3.05.Distributed-Tracing.md) -- [Service Proxy](3.06.Service-Proxy.md) +- [Service Registry](3.06.Service-Registry.md) - [WasmHost](3.07.WasmHost.md) - [FaaS](3.08.FaaS.md) - [Workflow](3.09.Workflow.md) diff --git a/docs/07.Reference/7.4.FaaSController.md b/docs/07.Reference/7.4.FaaSController.md new file mode 100644 index 0000000000..2831729926 --- /dev/null +++ b/docs/07.Reference/7.4.FaaSController.md @@ -0,0 +1,256 @@ +# FaaSController +- [Prerequisites](#prerequisites) +- [Configuration](#configuration) + - [Controller spec](#controller-spec) + - [FaaSFunction spec](#faasfunction-spec) + - [Lifecycle](#lifecycle) + - [RESTful APIs](#restful-apis) +- [Demoing](#demoing) +- [Reference](#reference) + +* A FaaSController is a business controller for handling Easegress and FaaS products integration purposes. It abstracts `FaasFunction`, `FaaSStore` and, `FaasProvider`. Currently, we only support `Knative` type `FaaSProvider`. The `FaaSFunction` describes the name, image URL, the resource, and autoscaling type of this FaaS function instance. The `FaaSStore` is covered by Easegress' embed Etcd already. +* FaaSController works closely with local `FaaSProvider`. Please make sure they are running in a communicable environment. Follow this [knative doc](https://knative.dev/docs/install/yaml-install/serving/install-serving-with-yaml/) to install `Knative`[1]'s serving component in K8s. It's better to have Easegress run in the same VM instances with K8s for saving communication costs. + + +## Prerequisites +1. K8s cluster : **v1.23+** +2. `Knative` Serving : **v1.3+** (with kourier type of network layer) + +## Configuration +### Controller spec +* One FaaSController will manage one shared HTTP traffic gate and multiple pipelines according to the functions it has. +* The `httpserver` section in spec is the configuration for the shared HTTP traffic gate. +* The `Knative` section is for `Knative` type of `FaaSProvider`. Depending your Kubernetes cluster, you can use either Magic DNS or Temporary DNS ([see](https://knative.dev/docs/install/yaml-install/serving/install-serving-with-yaml/#configure-dns)). Here's how you fill the `Knative` section for each one of them: + * **Temporary DNS**: The value of `networkLayerURL` can be found using the following command + + ``` bash + $ kubectl get svc -n kourier-system + NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE + kourier LoadBalancer 10.109.159.129 80:31731/TCP,443:30571/TCP 250dk + ``` + + The `CLUSTER-IP` with value `10.109.159.129` is your kourier's K8s service's address. Use it as the value for `networkLayerURL` in the YAML below. + * `hostSuffix`'s value should be `example.com` [2], like described in `Knative` serving's `Temporary DNS`. +* **Magic DNS**: For `networkLayerURL`, use the `EXTERNAL-IP` of the loadbalancer: + + ```bash + $ kubectl get svc -n kourier-system + NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE + kourier LoadBalancer 1.2.3.4 some-external-ip-of-my-cloud-provider.com 80:31060/TCP,443:30384/TCP 12m + ``` + * For `hostSuffix`, use `kn service list` to see the suffix of your functions: For url `http://demo.default.4.5.6.7.sslip.io` the `hostSuffix` is `4.5.6.7.sslip.io` (basically the IP `x.x.x.x` + `sslip.io`). **Note:** If you don't have any functions yet deployed, you first use any value for `hostSuffix` (for example `example.com`), then deploy a function and use `kn service list` to find out the value of `hostSuffix`. Update it to your configuration and re-create FaaSController. + +```yaml +name: faascontroller +kind: FaaSController +provider: knative # FaaS provider kind, currently we only support Knative + +syncInterval: 10s + +httpServer: + http3: false + port: 10083 + keepAlive: true + keepAliveTimeout: 60s + https: false + certBase64: + keyBase64: + maxConnections: 10240 + +knative: + networkLayerURL: http://{knative_kourier_clusterIP} # or http://{knative_kourier_externalIP} + hostSuffix: example.com # or x.x.x.x.sslip.com for Magic DNS +``` + +### FaaSFunction spec +* The FaaSFunction spec including `name`, `image`, and other resource-related configurations. +* The `image` is the HTTP microservice's image URL. When upgrading the FaaSfFunction's business logic. this field can be helpful. +* The `resource` and `autoscaling` fields are similar to K8s or `Knative`'s resource management configuration.[3] +* The `requestAdaptor` is for customizing the way how HTTP request content will be routed to `Knative`'s `kourier` gateway. + +```yaml +name: "demo10" +image: "dev.local/colordeploy:17.0" +port: 8089 +autoScaleType: "rps" +autoScaleValue: "111" +minReplica: 1 +maxReplica: 3 +limitCPU: "180m" +limitMemory: "100Mi" +requestCPU: "80m" +requestMemory: "20Mi" +requestAdaptor: + header: + set: + X-Func1: func-demo-10 # add one HTTP header +``` + +### Lifecycle +There four types of function state: Initial, Active, InActive, and Failed[4]. Basically, they come from AWS Lambda's status. + +* `Initial`: Once the function has been created in Easegress, its original state is `initial`. After checking FaaSProvider(Knative)'s status successfully, it will become `active` automatically. And the function is ready for handling traffic. + +* `Active`: Easegress's FaaSFunction will be `active` not matter there are requests or not. **Easegress will only route ingress traffic to FaaSProvider when the function is in the `active` state.** + +* `Inactive`: Stopping function execution by calling FaaSController's `stop` RESTful API and it will run into `inactive`. Updating function's spec for image URL or other fields, or deleting function also need to stop it first. + +* `Failed`: The function will be turned into `failed` states during the runtime checking. If it's about some configuration error, e.g., wrong docker image URL, we can detect this failure by function's `status` message and then update the function's spec by calling RESTful API. If it's about some temporal failure caused by FaaSProvider, the function will turn into the `initial` state after FaaSProvider is recovered. + +``` + Provision + + │ + │ + │ + ┌─────▼──────┐ Start ┌────────────┐ + │ │ Success │ │ + ┌────────┤ Initial ├────────────────► Active │ + │ │ │ │ ├──────┐ + │ └───┬───▲────┘ └────┬───▲───┘ │ + │ │ │ │ │ │ + │ │ │ │ │ │ + │ Errors│ │ Update Stop │ │ Start │ + │ │ ├───────────┐ │ │Success │ + │ │ │ │ │ │ │ + │ ┌───▼───┴────┐ │ ┌────▼───┴───┐ │ + │ │ │ └─────────┤ │ │ +Delete │ │ Failed │ │ Inactive │ │ + │ │ ◄────────────────┤ │ │ + │ └───┬───▲────┘ Start Failed └──────┬─────┘ │ + │ │ │ │ │ + │ │ │ Errors │ │ + │ Delete │ └────────────────────────────┼────────────┘ + │ │ │ + │ │ │ + │ ┌───▼────────┐ │ + │ │ │ Delete │ + └────────► Destory ◄───────────────────────┘ + │ │ + └────────────┘ +``` + +| Original State | Event | New State | +| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | +| initial | Checking the status in FaaSProvider by FaaSController automatically, and it's ready | active | +| initial | Checking the status in FaaSProvider by FaaSController automatically, and it's has some faults | failed | +| initial | Checking the status in FaaSProvider by FaaSController automatically, and it's waiting on all resources to become ready | initial | +| initial | Deleting the function by RESTful API | destroyed | +| active | Checking the status of instance in FaaSProvider by FaaSController automatically, and it has some faults | failed | +| active | Checking the status of instance in FaaSProvider by FaaSController automatically, and for something reason, some resources are missing or pending | failed | +| active | Stoping the function by RESTful API | inactive | +| active | Checking the status of instance in FaaSProvider by FaaSController automatically, and it's healthy | active | +| inactive | Updating the function by RESTful API | initial | +| inactive | Deleting the function by RESTful API | destroyed | +| inactive | Staring the function by RESTful API and after successfully checking the status in FaaSProvider by FaaSController automatically | active | +| inactive | Staring the function by RESTful API but failing at checking status in FaaSProvider by FaaSController automatically | failed | +| inactive | Staring the function by RESTful API, Checking the status of instance in FaaSProvider by FaaSController automatically, and for something reason, some resources are missing or pending | failed | +| failed | Updating the function by RESTful API | initial | +| failed | Deleting the function by RESTful API | destroyed | +| failed | Checking the status in FaaSProvider by FaaSController automatically, and it's ready again | initial | +| failed | Checking the status of instance in FaaSProvider by FaaSController automatically, and for something reason, some resources are missing or pending | failed | +| failed | Checking the status in FaaSProvider by FaaSController automatically, and it has some faults | failed | + + +### RESTful APIs +The RESTful API path obey this design `http://host/{version}/{namespace}/{scope}(optional)/{resource}/{action}`, + +| Operation | URL | Method | Body | Description | +| ----------------- | ------------------------------------------------------------------- | ------ | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| Create a function | http://eg-host/apis/v2/faas/{controller_name} | POST | function spec | When there is not such a function in Easegress | +| Start a function | http://eg-host/apis/v2/faas/{controller_name}/{function_name}/start | PUT | empty | When function is in `inactive` state only, it will turn-on accepting traffic for this function | +| Stop a function | http://eg-host/apis/v2/faas/{controller_name}/demo1/stop | PUT | empty | When function is in `active` state only, it will turn-off accpeting traffic for this function | +| Update a function | http://eg-host/apis/v2/faas/{controller_name}/{function_name} | PUT | function spec | When function is in `initial`, `inactive` or `failed` state. It can used to update your function or fix your function's deployment problem. | +| Delete a function | http://eg-host/apis/v2/faas/{controller_name}/{function_name} | DELETE | empty | When function is in `initial`, `inactive` or `failed` states. | +| Get a function | http://eg-host/apis/v2/faas/{controller_name}/{function_name} | GET | empty | No timing limitation. | +| Get function list | http://eg-host/apis/v2/faas/{controller_name} | GET | empty | No timing limitation. | + + +## Demoing +1. Creating the FaasController in Easegress + +```bash +$ cd ./easegress/example/primary-001 && ./start.sh + +$ ./egctl.sh create -f ./faascontroller.yaml + +$ ./egctl.sh get faas faascontroller +name: faascontroller +kind: FaaSController +provider: knative # FaaS provider kind, currently we only support Knative + +syncInterval: 10s + +httpServer: + http3: false + port: 10083 + keepAlive: true + keepAliveTimeout: 60s + https: false + certBase64: + keyBase64: + maxConnections: 10240 + +knative: + networkLayerURL: http://10.109.159.129 + hostSuffix: example.com +``` +2. Creating the function + +```bash + +$ curl --data-binary @./function.yaml -X POST -H 'Content-Type: text/vnd.yaml' http://127.0.0.1:12381/apis/v2/faas/faascontroller +``` + +3. Waiting for the function provisioned successfully. Confirmed by using `Get` API for checking the `state` field + +```bash +$ curl http://127.0.0.1:12381/apis/v2/faas/faascontroller/demo10 +spec: + name: demo10 + image: dev.local/colordeploy:17.0 + port: 8089 + autoScaleType: rps + autoScaleValue: "111" + minReplica: 1 + maxReplica: 3 + limitCPU: 180m + limitMemory: 100Mi + requestCPU: 80m + requestMemory: 20Mi + requestAdaptor: + host: "" + method: "" + header: + del: [] + set: + X-Func: func-demo + X-Func1: func-demo-10 + add: {} + body: "" +status: + name: demo10 + state: active + event: ready + extData: {} +fsm: null +``` + +4. Visiting function by HTTP traffic gate with `X-FaaS-Func-Name: demo10` in HTTP header. + +```bash +$ curl http://127.0.0.1:10083/tomcat/job/api -H "X-FaaS-Func-Name: demo10" -X POST -d ‘{"megaease":"Hello Easegress+Knative"}’ +V3 Body is +‘{megaease:Hello Easegress+Knative}’% + +$ curl http://127.0.0.1:10083/tomcat/job/api -H "X-FaaS-Func-Name: demo10" -X POST -d ‘{"FaaS":"Cool"}’ +V3 Body is +‘{FaaS:Cool}’% +``` +The function's API is serving in `/tomcat/job/api` path and its logic is displaying "V3 body is" with the contents u post. + +## Reference +1. knative website http://knative.dev +2. Install knative serving via YAML https://knative.dev/docs/install/yaml-install/serving/install-serving-with-yaml/ +3. resource quota https://kubernetes.io/docs/concepts/policy/resource-quotas/ +4. AWS Lambda state https://aws.amazon.com/blogs/compute/tracking-the-state-of-lambda-functions/ diff --git a/docs/imgs/architecture.png b/docs/imgs/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..87a60005452b83d4a55d2246685125bb6b2b2d60 GIT binary patch literal 44191 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 $ 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�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 C3|ZO$ literal 0 HcmV?d00001 diff --git a/docs/imgs/canary-release-case-3.png b/docs/imgs/canary-release-case-3.png new file mode 100644 index 0000000000000000000000000000000000000000..def2f242eeb9f342d115a658326a457368007e3e GIT binary patch literal 11742 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 literal 0 HcmV?d00001 diff --git a/docs/imgs/easegress-cluster-nodes.png b/docs/imgs/easegress-cluster-nodes.png new file mode 100644 index 0000000000000000000000000000000000000000..d4970474f0ebbb9b08912933eb93748be980d5ef GIT binary patch literal 17052 zcmeHv2UJsAw{BEaEEFp$0;0!uL?Mj?5s-u=KnR@>q6R}qAcPc3Dj=w!C@QuC*b9OU z#g0l7QPBeyK*b7(3W6dcARXS^;Q7z}&prPgZ@hBHeRsSuGGbVJ@3q&OYpyxJ`OUf9 z;_gb*89!@03Wd^fa&+)Sp+@GTP$TrT$3TnQqiAasYJ5>7g%v4Q35211)O;*?_}6@l zMX)p?az55!J_f^;$(98Qxk2Gval|qSKN6bYx>y=06bSi&!}nk;FzBUd^iqtKw*_%N z)*f#OUudgkSPYgud_OmcFZtOaewhVyu!zCs2_=!zh~UM;ckm;*LBqX~@$`ToAElT} z_LeBnBp0WUaKGVu6#VcAp;R(_8ydfixXg0+LZn*8A8rlgD}+4$aH|a78u4?4NDGz% zQ~Ws>7=rNU=keHlMGtpEV1@?-gfWy9 zcMR9v(T^%7IjAvoPgxiSgY}UyJ(y0 z32}6^(zu6+eI1?bDOd+9PnIK3Kz88!*gJ;GX_2J)7*enhdX{sEAp)vva6}N78XOQr z6HqWj7pk8=hg=c67tgC4mHXxEGNt zRasd$IpFO>8G$f3A0Hxx8yw=`XTeh_1B4_i30)RO<$B0eXr{>C%g+hAjU)$?xekG3 zABLKX@rDMfB{{@9)J5(Z>IQ$|)ks^U+)t$xTKIa~O9YW(rOJnBAr(+0Ay_;cFTlZ@ z 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 R{?`eV6UEixJZbHQ{{asVPe=d& literal 0 HcmV?d00001 diff --git a/docs/imgs/easegress.svg b/docs/imgs/easegress.svg new file mode 100644 index 0000000000..8593986331 --- /dev/null +++ b/docs/imgs/easegress.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/docs/imgs/readme-httpserver.png b/docs/imgs/readme-httpserver.png new file mode 100644 index 0000000000000000000000000000000000000000..aac6b5441a768b066d81c0d2548f766193297b88 GIT binary patch literal 50494 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
rY&`BV`_zG7X(?wfswXI9 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 OyCcqr?^%Cw<^KVmA(+wt literal 0 HcmV?d00001 diff --git a/docs/imgs/rss-pipeline.png b/docs/imgs/rss-pipeline.png new file mode 100644 index 0000000000000000000000000000000000000000..63b2bd068361518e85a4baaae35f2fec41d9cab2 GIT binary patch literal 27745 zcmb@tRajij6Fx{F5P~Pc3Bldn2@u>J2G<#6aJLYGyUU=#ZO{oW32ws>oI!&V+)2>o z`|a)S-u(AmoUX2}db{50ex5TubxyRVx&kg1ITi{E3a*l(tTqY?8Vm&m^#~K~Su@Vp z=ltAkX{zeVJv}|q$^3Zkq0s5~_4O$zC`3m`qwx8DOZ;o8>quV2492M$(N zR_N&H?CtIO`T5Js%l-ZR4GavLnwlg5L)U-r4P6jfSy|3sXLtAiPRyPa)oq=e-KG}( zo?p2b7(cG>*pmWg6AGlEGa0=Xucnpmy1Rea**`(0H;K!IkqG7Ca+=@#yQ9~PC6x~^ zXoM%0_H(Oxca1JDuI=VjcBK}zn0cnF*hFEmd!aH}V{o`pef;kDrJx)(VB?pi>zE*A z^aY30pI_5=czPok(!s9eQQJMM=~2WDNCt<@^w_k&LqTyAR+5$0^<6w%F-}aCBK@*{ z7FrlWVDZi5a*$%8x_Zu9+_C8KsbC9-_XYlI*%0rk8zpbcAKp1#3qIsn9eV$z=>Pf3 z9rZz{rWYe3o+CAB|CNnlk>OYX=Pnmj9Ui@qPmM>c+4v z#-4bg7P%N)xTxLvJ%19MqU=&I$zE@S7SDM%Em*7EQLrfOm`db!w-~kE4sSYk_FDes zoUH{*SG*Cnvd>0u7S4|!s9H{VV3t!gu+C`xvz7>o1RDQ2DQNR`%tjY)079qHB$c&x#0wQjL);yq181JxgBR+*ptq)G zLfbYl=hR>9u8Tle`=tE)Y;=s>MX-K!<`g@(@HCK*E@73)>CN>2I&2NoCSG>Lq$1Nm zTS7~*74W9>S==PgII6M0-L8GXgD33h?>#hXiys?`1YZQe{_Uam8$rqDrR@j2G=IvE zXMBq8(xpGLVrO;RF>!%n2R~&1L%hgO+tS<58E%t0Dmjk^qZB14p-&wj==291HGjTe za~A11zQ7hJcmrZ?aQUTTl69pE+fP#FTKBTH$hgz+UY#Ct=!UwtZ3ji0nWR4!=)RY| zw1HJAey7YS``aRb(JD2RccY5_d;UN1rXVa`%EA$+zSG+9by)Kt3Y#o}K59q}&(HG& zO9p~Pax^G`wBhTQ&m&`E5$RHP&!#djpGOwR(b!K&^-&9Jcv|i7&4TXznT(e2n3+$! zPl~pe{T}~ap`k-JroF-h=CTe9unbqnxvpETu2%wtI9I#ms1A>hTC^@1)5;fVAO9pK z7wj}-R;z=Fs)XYBS`J!1^Ol{ib;}yGxLDvO))on|;zax2CgB#Z?wMi_xy*6MU)?qk zb(sFRO63WEx?*W>4?sKk>q^w6FRf^k^a04f6qP!Cc>TddL42%sSIhH3JDfVX0oabz z&5Z!I+_3rwqaFBBNa)hIhA?v0E9d%>UR_R9&bQOcjs5`+S+lRxX}~@%bhOwqSu*4N z48b*I1~E%$Rvr2JqPXOq&D{hu0@(hkWYbm@H-RtA6=`TZCk`jV`WS-+PRzUS|5hbR z(c-i9MJRIU1X)8_k{qMDGtOhW_w5U&U&CeyuD%WABHM4;%hl!%`!0?K^2Z;?0|Wnw z3$~dPq=wYn$ouVRpJV=b(AsXyWS$=^=#0#93cHr<;LgaBCD11ObhV!pi;6ChRC7@% zc#qum7cmlqV-@X{O#|s}%~*o$9k18_om#n^-;W3S|KkP3)o%M+$gL6>LPBhR*T)Y6jybF4R~WCq8WBnq&j^^K1t^HHnmYlf@Ow)Wr)& zzB^Sg8LgRuf1AguBtnFaFRZ+`~ zv+%c6JxWCQSv3&)I$q~5o02fa5m)$e?A~I8!S1g(^@WR031?}`cTDX(9mxWxJbf#h z)I@G-YqZ)QHP+p<9lh6qc{Kz@1EXRUGiWfxKa#;gQ?~xa=P9zsm83Mor-3R4@nGys ztk|CytV)r|wnQAiVEWRvb49V2o4j7fxS^Tb8|2r*`#i(2adC^97dWbt_Qjw%GUwdsq>? zR=k*G(xbw4^DlmBvY$*~x|e_u?Ul=izq=vI~J9YW-$=zPSe3!M+0Z zNNNAIo4@x7)=ocS>{-RFLXz(D?XwbRBn7ORQ%Mw6qodA<-2LW?La$4uqWiN~HVI-A z6*k?owgqRJTiu~un?hYS!Ek)zfP2swbV#yZ{1+D8=0w3)*K-KCw66`E2;^k-*c#^^ z3j)zj*4X}=J>1Vr0#Mo9#MX}<(+ii!&b=18th;FadAJtu@|1A@ajb9L%5~ZFBjqsj z)Z4`XSR_YS@1YU(QmB1%2q?o};7;8TCO9_HH(ulu6!{@DVvv46_dBYr8Vom8&Z1d@ z5$@`gErG>xV&TmO1>_hc1zTOmQHa;@wjH$!snV|pz(4}d&IzFa|5h!_m6iO7^XBW{ z!X4grQEp7HU*|^)I9|;BV*=vuOUF-c}>qVF4LOo>eb~c`HWq+^^vE; zIt({ru$6T=AtzjX1w4&~un%w~*3*9;etYp9xIIvwkYz^I=)3fDKM$$Cj-eSMS|%dF!7i{>5=THl@N=w|0Nt{Tn`4%q?e4k;!6G`O zt2=U><0RkvzVv>7HRl({A)l&7(yl;9pJr=e_q%WHS}-ug@wa?URY(K|tmg9yY3c3& zgu(XMdi>U8WT#-)Z(OwI`X?GEPfhbC(5)J{3pFZ)be&QfW=V{xMdpF)j_$Twn5H_6 zbCIp)VHf*FyldDtoqx;6=X_r*b1|;W=l|`94wbq8=w}5Z>G@;hGCZtqwai{E|7#-O z&d=cvmwRbN;#K%3b_8_c?z6bPH-a`McoA%LH|wH);o&m38vHpOh-7hw*2RCoxf@($! ziPF$Atq!}~^Visc3ts`n?BSxS1-9R0yL=bHaiOZJP2v4!;Lp+Kyc9!rL$NzXkSrN^ zb`U451zEFN3#`gI0fBE#$%D`ymx(#9?Q?_k#bD?U*?roQynS=^nyKJf3T=>T0Z}V! zzT^{e5eW?0`je{LQO61Lcjjv>jC3us`z6PqnQf^RoGN34Lt6OWg0{-ObOTHNla5Z* z*?I77z`yORpsqH7*v#mS@V7ZH4BT73%u~#ED3C2(;t{}bK1Yp zVEB)L$=EBAF8603+iY8#lt1}DqQC=`rTRgjlSBW-h(hkexeq+}%qBF0 znMsVT3r|l9Ic}@TUAcoq4)Q7!0X&Yv5(_C*=POJcUrm2g0ay4vZ_P8mCzR0=l50nK z9oK&sWO{3RePLbWX>5z3p|@5)0N+_rXZ5LL%e@2`-fXR5NehW}VSTFN2tm?fbHtf5 zbVsFY^$jgh>2CwiL`U22w%An}hICthd534^Pq4zy;wBEox#uxFN9t!?_N9na#Y-l7 z+nJs+;9#^m_R3~@pfUc~l9b7H+5KWPAhQrOrlu z(6H{Pc)_pX*Jq)UDi*}ZWPpti@L&ZJj`s2e3>XCf(3A}gYS$k57m;x}1~>^n{`Ovf z8X2x<;Lf=s7QF<(+3#95;EfJ!2=MJ2AWubDxlDL7TYnmW=Hrq3vyVyuNcge8k@G>i z1iT$^}ho(>DF7D*Hat1WkkoK&K8NJFk2PD$gUpx}gYKh-i>2G@E?^^UC;YZ0u9k)f}5rT!lX0}Td4f+#XP zII=4b(stT!z8Z!+7|2jJ^#3tSUQMaNG;y(Ee*OP_1Dpf|)SQ?lf3e7^G5cYi`10iv z82Ertl>I)ge+L|pUjh#bu?a2%tAk)NeNKX#IYi_xu-R@v@|j@H*?7#MF|nz#rq_|eZ|4GE9U}7zOe7hj&_(aA zI-mf{9`;{=zch~ehTK$M*SE+2oT@***0|NF?|SeE#{J423*&ml0qR7% zySph*^|@ErHsi%6FV++qNrA1uAqqE{oGN)bA-!crXC`-aaiyj!`otT?woaJOtvv&L zsQ3}%DT$>NY#CAD_m01+Q>NO&c}EDCraxYZQ*sfjCMTh(_L9ejO9LE8DI^iXi_iJ>|)% zx>n;N$a4f3otu4?8html2J9+b+B5DeHScPT@AjahmAxo~dIeXKni%p*aE9K7K|TgoiZ3@sO|*U9eL3WFsRtPlQx0BDGU6D^@p-n4x#&fO zgy!@fHuLIq2+sI-h!k4AkjdAe`1mkJn`u!-_cGj!D~6wc^J~~A!HCh>Lv5+?A)QvWurKO&4a#Dy z&gR+3xGe0^UkLE}KHt)EX|MQcr*==(heTHU(EKJG-n|&dhyl?QcAA{k`QA2O>Mdm?9F2^VlvC5*Pfj9 z4gIGGd*Jg4h$mJ#9G+KT+F7pA3vCrnoZNVM{S*3Nu)%EhxNJz+7#klX1}idTX%5Bv z%|2jC{@Q>{t4ljW){ef0(93aBLMgY7=x2Im0SEuws+t%~{%RF|OdLC~E!F&L_XTQ2 z*NS84T`6_E*;O+lmPc$RObB$^tQM10ExKq!_GWAqUB>7i9cOQCX*Z?Iz9X)Pre<67>aua7Vm`yOW=kVJ=ww2=|7zhNdOz&D;}E&X{JnKpdZ1R4agh6MG4+phlCi(&c+h(!C+;3@3^Z)YhDT&WhcJG(IFa!v*V*m zM|w1ZznN;Pm3b@%d?I+iium@TOs%d-Maay&$!}v3De)uyanmtheB(CA~PSlQrNpy$J_(d@e`I8A}_4>An z!xZT0s~n^L)-yCn{Ha}-X;$4O3=W9t6QtVf+d}UVe0vkdV*B(E(5pce+AGJFwLfCt z^EDpup@d6Zi3$ijBOJ1P3mZPkQp<<4=bOGa_}xxMW!(EI-i7`B^`$gFp5W80^1L*G z=MS`1K}@W~@S3s-aI;!b@GQk{R2J_n3~Noey@CwOuy_&Q)LWvJ$gs)(^n6a`?!6 zX9FZw@v@C>gkCq8U~Gk=TF$ z#9-LDl%W(+x2k9xAOlchjjJC9kweQ( z9pb^h`2*kSrb5-$m||mUy71yjziU|tcV`9B0DfM`-%gHBFu_&+s{aXc@dQZ zOuRY%X8g&3iCBg1V6zzV4~?t8ykBZ!9&``ewOO`9QWD?$A~FKA%=UIJWwe7xL# zm6xF!&ZLa=O+Hg*o|HOD5URs4y@ekmYS=fQN!$LI`NgHiAsGvZa|ilBN;z1lcquBN z!iRN}P4d0{?8cy2RyHIg-ko0N^G>DVe#g07Uy&vg7@o00z%BM+5DWzcr@zpPNyyc< zTX{aPMhM>Z-BqzyduFY!XP)Kd9yfavvO6B(4;3@7@EqEMoR1|al z9e4yQTlt4Rwt&>;8cc>%5o3@FV#m;5jyT3JB8>mOwAA3}T!n^J@B36}GXaAhUywn! zUDhQ7ANSbGgR=mS5YeG8DeOZ?)6wVGy!cd+{C_@5Oj2r(h>sU6#6q8LP~q7xflt`+ z+{T#UGG2*DO)skv9!KahX|GdkAG}!?&x|OnOXApxSyj0NvQf5ou)w^U0es{$%F<4 z_uZ+#511f}EG3ib6Df*OpI3vO0KKVj>5hoSJyRDa%QnAnX8&W?`~^ieWY&B&kQ|l| z0c_oD6=uW(`hIKsWg^Y+n7+0(%JmWO<^iI3R4O+xj8tEC$Tw0)T)cP=CF>jnJh2Dr z2m7tq!)s4?I{+H{^U2o)X)nYE!E4V88W94%Xmt~{ z$e_Xp5nC^GpNx>Yo0=}!zF5d3^|9Ug=8$+*?}uvXXZ_>Rt66Fz+zy>0s_c>@Y=@DZ%W?UN$N z0O-_nm)P&o+z8uS?dmSyXPKmYT6>B&V!?@!mdBOV%z03M(<_9ceI!Mm5kG&}4&i|p!N&2dj5^lcW#T?#kSes8q5i!3qmm9l4$ zu+9(HP>x)#APO4G(HF!AlV)tIFrR^q>bjgzdH?zH^0~Y$_3LT=0W*l;d)(Kv)iGw$ldk`FfwIF> z3ia8CdhhArbC@Kik$jKui7jE=joJU1kkBP+C`}Mm#2vWJ85k=FiBD0}u_R4-ef3yJ zh<P$N*+SgT$Z?TBFKUwn?B9dtu1&UgTKZm>Lcgk(=*G6wjn0Gy|%M*~X z=T-O0G4ATmkD=SOLk2(7&-8L4<*>xk$6*hp)wD!@wF=g^CIiyl>r;GNIr)ke!f(%+ zU6aD~n(_E!H*2@HBQCWNKihIS#?sxDMpGUOSz~(t`ad~{^%pu}Js*7TLgA_uWfX3bnQEjhb9_{C5m?N zm*qJ*z3JiSle3~jI17I`CA1fV(qjpt6D?s3$<>Hz&_|O0tS^KAJSoC_g&gbt;8y+`#n?Zen~rPr?-5;R^s z@knmLU$Qg&^s~WM~TS#m1yZ zj{>?&I;yH0@hxX0@}t=c3-Y6}wzqh43QC)ANAs7po8DT) zqs(KLC{xVT+wrV}lUgEW7dV10Q_py2ko`y65l8l*B~-(JQGoO@^|UQwaFW$rQ~

  • 08zrjo~UbVj3?T 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
  • O=X!@GYmvNEVL30cladXVk;6_CCD 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

    q_cX$qG@dNL%WK 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