# Technical Documentation: easegress-io/easegress

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/easegress-io/easegress)

### Overview (README.md)

# Easegress  

[](https://goreportcard.com/report/github.com/easegress-io/easegress)
[](https://github.com/easegress-io/easegress/actions/workflows/test.yml)
[](https://codecov.io/gh/easegress-io/easegress)
[](https://hub.docker.com/r/megaease/easegress)
[](https://opensource.org/licenses/Apache-2.0)
[](https://github.com/easegress-io/easegress/blob/main/go.mod)
[](https://cloud-native.slack.com/messages/easegress)
[](https://www.bestpractices.dev/projects/8265)
[](https://app.fossa.com/projects/git%2Bgithub.com%2Feasegress-io%2Feasegress?ref=badge_shield)

<a href="https://github.com/easegress-io/easegress">
    <img src="./docs/imgs/easegress.svg"
        alt="Easegress logo" title="Easegress" height="175" width="175" align="right"/>
</a>

- [What is Easegress](#what-is-easegress)
- [Features](#features)
- [Getting Started](#getting-started)
  - [Launch Easegress](#launch-easegress)
  - [Reverse Proxy](#reverse-proxy)
- [Use Cases](#use-cases)
- [Documentation](#documentation)
- [Easegress Portal](#easegress-portal)
  - [Screenshots](#screenshots)
- [Community](#community)
- [Contributing](#contributing)
- [License](#license)

## What is Easegress

`Easegress` is a Cloud Native traffic orchestration system designed for:

- **High Availability:** Built-in Raft consensus & leader election provides 99.99% availability.
- **Traffic Orchestration:** Simple orchestration of various filters for each traffic pipeline.
- **High Performance:** Lightweight and essential features speed up the performance.
- **Observability:** There are many meaningful statistics periodically in a readable way.
- **Extensibility:** It's easy to develop your own filter or controller with high-level programming language.
- **Integration:** The simple interfaces make it easy to integrate with other systems, such as Kubernetes Ingress, [EaseMesh](https://github.com/megaease/easemesh) sidecar, Workflow, etc.

The architecture of Easegress:



And you can check [Easegress DeepWiki Page](https://deepwiki.com/easegress-io/easegress) to dive into more details.

## Features

- **Service Management**
  - **Multiple protocols:**
    - HTTP/1.1
    - HTTP/2
    - HTTP/3(QUIC)
    - MQTT
  - **Rich Routing Rules:** exact path, path prefix, regular expression of the path, method, headers, clientIPs.
  - **Resilience&Fault Tolerance**
    - **CircuitBreaker:** temporarily blocks possible failures.
    - **RateLimiter:** limits the rate of incoming requests.
    - **Retry:** repeats failed executions.
    - **TimeLimiter:** limits the duration of execution.
  - **Deployment Management**
    - **Blue-green Strategy:** switches traffic at one time.
    - **Canary Strategy:** schedules traffic slightly.
  - **API Management**
    - **API Aggregation:** aggregates results of multiple APIs.
    - **API Orchestration:** orchestrates the flow of APIs.
  - **Security**
    - **IP Filter:** Limits access to IP addresses.
    - **Static HTTPS:** static certificate files.
    - **API Signature:** supports [HMAC](https://en.wikipedia.org/wiki/HMAC) verification.
    - **JWT Verification:** verifies [JWT Token](https://jwt.io/).
    - **OAuth2:** validates [OAuth/2](https://datatracker.ietf.org/doc/html/rfc6749) requests.
    - **Let's Encrypt:** automatically manage certificate files.
  - **Pipeline-Filter Mechanism**
    - **Filter Management:** makes it easy to develop new filters.
  - **Service Mesh**
    - **Mesh Master:** is the control plane to manage the lifecycle of mesh services.
    - **Mesh Sidecar:** is the data plane as the endpoint to do traffic interception and routing.
    - **Mesh Ingress Controller:** is the mesh-specific ingress controller to route external traffic to mesh services.
        > Notes: This feature is leveraged by [EaseMesh](https://github.com/megaease/easemesh)
  - **Third-Part Integration**
    - **FaaS** integrates with the serverless platform Knative.
    - **Service Discovery** integrates with Eureka, Consul, Etcd, and Zookeeper.
    - **Ingress Controller** integrates with Kubernetes as an ingress controller.
- **Extensibility**
  - **WebAssembly** executes user developed [WebAssembly](https://webassembly.org/) code.
- **High Performance and Availability**
  - **Adaption**: adapts request, response in the handling chain.
  - **Validation**: headers validation, OAuth2, JWT, and HMAC verification.
  - **Load Balance:** round-robin, random, weighted random, IP hash, header hash and support sticky sessions.
  - **Cache:** for the backend servers.
  - **Compression:** compresses body for the response.
  - **Hot-Update:** updates both config and binary of Easegress in place without losing connections.
- **Operation**
  - **Easy to Integrate:** command line([egctl](docs/02.Tutorials/2.1.egctl-Usage.md)), Easegress Portal, HTTP clients such as curl, postman, etc.
  - **Distributed Tracing**
    - Built-in [OpenTelemetry](https://opentelemetry.io/), which provides a vendor-neutral API.
  - **Observability**
    - **Node:** role(primary, secondary), raft leader status, healthy or not, last heartbeat time, and so on
    - **Traffic:** in multi-dimension: server and backend.
      - **Throughput:** total and error statistics of request count, TPS/m1, m5, m15, and error percent, etc.
      - **Latency:** p25, p50, p75, p95, p98, p99, p999.
      - **Data Size:** request and response size.
      - **Status Codes:** HTTP status codes.
      - **TopN:** sorted by aggregated APIs(only in server dimension).
- **AI Integration**
  - **Proxy:** proxy requests to LLM providers like OpenAI, DeepSeek, Anthropic, etc.
  - **Anthropic API Adaption:** adapts requests and responses in Anthropic API to OpenAI format.
  - **Vector Database:** integrates with vector databases for caching.
  - **Monitoring:** provides insights into the performance and usage of AI models.

## Getting Started

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](docs/01.Getting-Started/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.

More about getting started with Easegress:

- [Quick Start](docs/01.Getting-Started/1.1.Quick-Start.md)
- [Install Easegress](docs/01.Getting-Started/1.2.Install.md)
- [Main Concepts](docs/01.Getting-Started/1.3.Concepts.md)

## Use Cases

The following examples show how to use Easegress for different scenarios.

- [API Aggregation](docs/02.Tutorials/2.3.Pipeline-Explained.md#api-aggregation) - Aggregating many APIs into a single API.
- [Cluster Deployment](docs/05.Administration/5.1.Config-and-Cluster-Deployment.md) - How to deploy multiple Easegress cluster nodes.
- [Canary Release](docs/03.Advanced-Cookbook/3.04.Canary-Release.md) - How to do canary release with Easegress.
- [Distributed Tracing](docs/03.Advanced-Cookbook/3.05.Distributed-Tracing.md) - How to do APM tracing  - Zipkin.
- [FaaS](docs/03.Advanced-Cookbook/3.09.FaaS.md) - Supporting Knative FaaS integration
- [Flash Sale](docs/03.Advanced-Cookbook/3.09.FaaS.md) - How to do high concurrent promotion sales with Easegress
- [Kubernetes Ingress Controller](docs/04.Cloud-Native/4.1.Kubernetes-Ingress-Controller.md) - How to integrate with Kubernetes as ingress controller
- [LoadBalancer](docs/02.Tutorials/2.3.Pipeline-Explained.md#load-balancer) - A number of the strategies of load balancing
- [MQTTProxy](docs/03.Advanced-Cookbook/3.01.MQTT-Proxy.md) - An Example to MQTT proxy with Kafka backend.
- [Multiple API Orchestration](docs/03.Advanced-Cookbook/3.03.Multiple-API-Orchestration.md) - An Telegram translation bot.
- [Performance](docs/03.Advanced-Cookbook/3.11.Performance.md) - Performance optimization - compression, caching etc.
- [Pipeline](docs/02.Tutorials/2.3.Pipeline-Explained.md) - How to orchestrate HTTP filters for requests/responses handling
- [Resilience and Fault Tolerance](docs/02.Tutorials/2.4.Resilience.md) - CircuitBreaker, RateLimiter, Retry, TimeLimiter, etc. (Porting from [Java resilience4j](https://github.com/resilience4j/resilience4j))
- [Security](docs/02.Tutorials/2.5.Traffic-Verification.md) - How to do authentication by Header, JWT, HMAC, OAuth2, etc.
- [Service Registry](docs/03.Advanced-Cookbook/3.06.Service-Registry.md) - Supporting the Microservice registries - Zookeeper, Eureka, Consul, Nacos, etc.
- [WebAssembly](docs/03.Advanced-Cookbook/3.07.WasmHost.md) - Using AssemblyScript to extend the Easegress
- [WebSocket](docs/02.Tutorials/2.6.Websocket.md) - WebSocket proxy for Easegress
- [Workflow](docs/03.Advanced-Cookbook/3.10.Workflow.md) - An Example to make a workflow for a number of APIs.

For full list, see [Tutorials](docs/02.Tutorials/README.md) and [Cookbook](docs/03.Advanced-Cookbook/README.md).

## Documentation

- [Getting Started](docs/01.Getting-Started/README.md)
- [Tutorials](docs/02.Tutorials/README.md)
- [Advanced Cookbook](docs/03.Advanced-Cookbook/README.md)
- [Cloud Native](docs/04.Cloud-Native/README.md)
- [Administration](docs/05.Administration/README.md)
- [Development](docs/06.Development-for-Easegress/README.md)
- [Reference](docs/07.Reference/README.md)

## Easegress Portal

[Easegress Portal](https://github.com/easegress-io/easegress-portal) is an intuitive, open-source user interface for the Easegress traffic orchestration system. Developed with React.js, this portal provides config management, metrics, and visualizations, enhancing the overall Easegress experience.

### Screenshots

**1. Cluster Management**



**2. Traffic Management**



**3. Pipeline Management**



## Community

- Open [GitHub issues](https://github.com/easegress-io/easegress/issues/new/choose) for bugs, feature requests, questions, and documentation problems.
- Join the [Easegress Slack channel](https://cloud-native.slack.com/messages/easegress) for public development discussion.
- Report security vulnerabilities privately to Yun Long <longdeqidao@gmail.com>.

### Community Tools

- [KubeStellar Console Guided Install](https://console.kubestellar.io/missions/install-easegress) - Step-by-step guided installation with pre-flight checks, validation, troubleshooting, and rollback.

## Contributing

See [Contributing guide](./CONTRIBUTING.md#contributing). The project welcomes contributions and suggestions that abide by the [CNCF Code of Conduct](./CODE_OF_CONDUCT.md).

## License

Easegress is under the Apache 2.0 license. See the [LICENSE](./LICENSE) file for details.

[](https://app.fossa.com/projects/git%2Bgithub.com%2Feasegress-io%2Feasegress?ref=badge_large)

---

### README (`docs/README.md`)

# 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)

---

### 07.Reference README (`docs/07.Reference/README.md`)

# Reference

### [Controllers](7.01.Controllers.md)
### [Filters](7.02.Filters.md)
### [Ingress Controller](7.03.Ingress-Controller.md)
### [FassController](7.04.FaaSController.md)
### [Stream](7.05.Stream.md)
### [Routers](7.06.Routers.md)
### [Benchmark](7.07.Benchmark.md)
### [Metrics](7.08.Metrics.md)

---

### 06.Development For Easegress README (`docs/06.Development-for-Easegress/README.md`)

# Development for Easegress

### [Developer Guide](6.1.Developer-Guide.md)
### [Custom Data](6.2.Custom-Data.md)
### [egbuilder](6.3.egbuilder.md)

---

### 05.Administration README (`docs/05.Administration/README.md`)

# Administration

### [Configuration & Cluster Deployment](5.1.Config-and-Cluster-Deployment.md)
### [Kernel Tuning](5.2.Kernel-Tuning.md)

---

### 04.Cloud Native README (`docs/04.Cloud-Native/README.md`)

# Cloud Native

### [Kubernetes Ingress Controller](4.1.Kubernetes-Ingress-Controller.md)
### [Kubernetes Gateway API](4.2.Gateway-API.md)
### [EaseMesh](4.3.EaseMesh.md)

---

### 03.Advanced Cookbook README (`docs/03.Advanced-Cookbook/README.md`)

# Advanced Cookbook

### [MQTT Proxy](3.01.MQTT-Proxy.md)
### [Flash Sale](3.02.Flash-Sale.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 Registry](3.06.Service-Registry.md)
### [WasmHost](3.07.WasmHost.md)
### [ChatGPT Bot](3.08.ChatGPT-Bot.md)
### [FaaS](3.09.FaaS.md)
### [Workflow](3.10.Workflow.md)
### [Performance](3.11.Performance.md)
### [Migrate v1.x Filter To v2.x](3.12.Migrate.md)
### [Nginx Configuration Conversion to Easegress YAML](3.13.Nginx.md)

---

### 02.Tutorials README (`docs/02.Tutorials/README.md`)

# 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)
- [Traffic Verification](2.5.Traffic-Verification.md)
- [Websocket](2.6.Websocket.md)
- [gRPC](2.7.gRPC.md)
- [HTTPS & Let's Encrypt](2.8.HTTPS-Lets-Encrypt.md)
- [AI Gateway](2.9.AI-Gateway.md)
- [Claude Proxy](2.10.Claude-Proxy.md)
- [Nginx Header Variables](2.11.Nginx-Header-Vars.md)

---

### 01.Getting Started README (`docs/01.Getting-Started/README.md`)

# Getting Started

### [Quick Start](1.1.Quick-Start.md)
### [Install](1.2.Install.md)
### [Concepts](1.3.Concepts.md)

---

### Doc Ingresscontroller (`doc/ingresscontroller.md`)

This document has been moved to [here](../docs/07.Reference/7.03.Ingress-Controller.md).

---

### Doc Cookbook K8s Ingress Controller (`doc/cookbook/k8s-ingress-controller.md`)

This document has been moved to [here](../../docs/04.Cloud-Native/4.1.Kubernetes-Ingress-Controller.md).

---

### Doc Reference Ingresscontroller (`doc/reference/ingresscontroller.md`)

This document has been moved to [here](../../docs/07.Reference/7.03.Ingress-Controller.md).

---

### 07.Reference 7.01.Controllers (`docs/07.Reference/7.01.Controllers.md`)

# Controllers 

- [System Controllers](#system-controllers)
  - [ServiceRegistry](#serviceregistry)
  - [TrafficController](#trafficcontroller)
  - [RawConfigTrafficController](#rawconfigtrafficcontroller)
    - [HTTPServer](#httpserver)
      - [AccessLogVariable](#accesslogvariable)
    - [GRPCServer](#grpcserver)
    - [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)
  - [AIGatewayController](#aigatewaycontroller)
  - [WAFController](#wafcontroller)
- [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)
  - [AIGatewayController.ProviderSpec](#aigatewaycontrollerproviderspec)
    - [Supported Providers](#supported-providers)
  - [AIGatewayController.MiddlewareSpec](#aigatewaycontrollermiddlewarespec)
  - [AIGatewayController.SemanticCacheSpec](#aigatewaycontrollersemanticcachespec)
  - [AIGatewayController.EmbeddingSpec](#aigatewaycontrollerembeddingspec)
  - [AIGatewayController.VectorDBSpec](#aigatewaycontrollervectordbspec)
  - [AIGatewayController.RedisSpec](#aigatewaycontrollerredisspec)
  - [AIGatewayController.PostgresSpec](#aigatewaycontrollerpostgresspec)
  - [AIGatewayController.QdrantSpec](#aigatewaycontrollerqdrantspec)
  - [WAFController.RuleGroupSpec](#wafcontrollerrulegroupspec)
  - [WAFController.RuleSpec](#wafcontrollerrulespec)
  - [WAFController.IPBlockerSpec](#wafcontrolleripblockerspec)
  - [WAFController.GeoIPBlockerSpec](#wafcontrollergeoipblockerspec)

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](7.06.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](7.05.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 |

##### 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                                      |

#### 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.

``` yaml
name: server-grpc
kind: GRPCServer
port: 8080

# 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:
# Rules for host matching.
# If not match, GRPCServer will check next rule.
- host: <your-host>
  hostRegexp: <your-host-regexp>

  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:
  ...
```

##### 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 |
|------|------|-------------|----------|
| 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 |
| ipFilter | [ipfilter.Spec](#ipfilterspec) | IP Filter for all traffic | No |
| rules | [][grpcserver.Rule](#grpcserverrule) | Router rules | No |

#### 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](../02.Tutorials/2.4.Resilience.md) 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](7.02.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
    ...

# fallthrough controls the error handling behavior in different pipelines.
# specifying whether subsequent stages should execute despite encountering errors in earlier stages.
# By default, encountering an error in either the beforePipeline or the pipeline stages halts the entire process.
fallthrough:
  # false indicates that if an error occurs in the beforePipeline, the pipeline will not execute.
  beforePipeline: false
  # false means that if an error occurs in the pipeline, the afterPipeline will not execute.
  pipeline: false
---
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](7.04.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: <http://127.0.0.1:8761/eureka>) |
| 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: <token value>
```

| 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                                |

### AIGatewayController

AIGatewayController is a controller for managing AI Gateway resources. It provides control plane functionality for the AI Gateway, allowing Filter AIGatewayProxy to leverage AI models for processing requests and responses.

```yaml
kind: AIGatewayController
name: AIGatewayController
providers:
    - name: openai-provider
      providerType: openai
      baseURL: https://api.openai.com
      apiKey: sk-proj-openai-api-key # Replace with your OpenAI API key
    - name: deepseek-provider
      providerType: deepseek
      baseURL: https://api.deepseek.com
      apiKey: sk-deepseek-api-key # Replace with your DeepSeek API key
version: easegress.megaease.com/v2
```

| Name        | Type                                      | Description                                           | Required |
| ----------- | ----------------------------------------- | ----------------------------------------------------- | -------- |
| providers   | [][ProviderSpec](#aigatewaycontrollerproviderspec)           | List of AI providers configuration                    | No       |
| middlewares | [][MiddlewareSpec](#aigatewaycontrollermiddlewarespec)       | List of middleware configuration for request processing | No       |


### WAFController

```yaml
name: waf-controller
kind: WAFController
ruleGroups:
- name: sqlinjection
  rules:
    customRules: |
      // https://github.com/corazawaf/coraza-coreruleset/blob/main/rules/%40crs-setup.conf.example
      // check coraza core rule set recommend setup file 
    owaspRules:
    - REQUEST-901-INITIALIZATION.conf
    - REQUEST-942-APPLICATION-ATTACK-SQLI.conf
    - REQUEST-949-BLOCKING-EVALUATION.conf
- name: geoipblocker
  rules:
    geoIPBlocker:
      dbPath: <your-dir>/Country.mmdb
      dbUpdateCron: "0 0 1 * *"
      deniedCountries:
        - XX
```


| Name        | Type                                      | Description                                           | Required |
| ----------- | ----------------------------------------- | ----------------------------------------------------- | -------- |
|ruleGroups	| [][RuleGroupSpec](#wafcontrollerrulegroupspec) |	A list of configurations for one or more WAF rule groups. |	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 or wildcard. For example "*.example.com" or "www.example.com".                                          | 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. Wildcard is supported.                                                    | 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](7.05.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](#filtersfilter) | 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](7.02.Filters.md).

| Name                                 | Type   | Description    | Required |
| ------------------------------------ | ------ | -------------- | -------- |
| name                                 | string | Name of filter | Yes      |
| kind                                 | string | Kind of filter | Yes      |
| [self-defining fields](7.02.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                      |
| ------- | -------- | ---------------- | ----------------------------- |
| 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 policies in [the Resilience Policy documentation](../02.Tutorials/2.4.Resilience.md).

### AIGatewayController.ProviderSpec

| Name         | Type              | Description                                                    | Required |
| ------------ | ----------------- | -------------------------------------------------------------- | -------- |
| name         | string            | Unique name of the provider                                    | Yes      |
| providerType | string            | Type of the provider (see below)                              | Yes      |
| baseURL      | string            | Base URL for the provider API                                  | Yes      |
| apiKey       | string            | API key for authentication                                     | Yes      |
| headers      | map[string]string | Additional headers to include in requests                      | No       |
| endpoint     | string            | Endpoint URL (used for Azure OpenAI)                          | No       |
| deploymentID | string            | Deployment ID (used for Azure OpenAI)                         | No       |
| apiVersion   | string            | API version (used for Azure OpenAI)                           | No       |

#### Supported Providers

The providerType can be one of the following:

- anthropic
- azure
- bedrock
- cohere
- deepseek
- gemini
- mistral
- ollama
- openai
- qwen

### AIGatewayController.MiddlewareSpec

| Name          | Type                                        | Description                                    | Required |
| ------------- | ------------------------------------------- | ---------------------------------------------- | -------- |
| name          | string                                      | Unique name of the middleware                  | Yes      |
| kind          | string                                      | Type of middleware (e.g., SemanticCache)      | Yes      |
| semanticCache | [SemanticCacheSpec](#aigatewaycontrollersemanticcachespec) | Configuration for semantic cache middleware | No |

### AIGatewayController.SemanticCacheSpec

| Name            | Type                                      | Description                                           | Required |
| --------------- | ----------------------------------------- | ----------------------------------------------------- | -------- |
| embeddings      | [EmbeddingSpec](#aigatewaycontrollerembeddingspec) | Configuration for embedding provider          | Yes      |
| vectorDB        | [VectorDBSpec](#aigatewaycontrollervectordbspec) | Configuration for vector database               | Yes      |
| readOnly        | bool                                      | Whether the cache is read-only                        | No       |
| contentTemplate | string                                    | Template for extracting content from requests         | No       |

### AIGatewayController.EmbeddingSpec

| Name         | Type              | Description                                    | Required |
| ------------ | ----------------- | ---------------------------------------------- | -------- |
| providerType | string            | Type of embedding provider                     | Yes      |
| baseURL      | string            | Base URL for the embedding API                 | Yes      |
| apiKey       | string            | API key for authentication                     | Yes      |
| headers      | map[string]string | Additional headers to include in requests      | No       |
| model        | string            | Model name for embeddings                      | Yes      |

### AIGatewayController.VectorDBSpec

| Name           | Type                                     | Description                                    | Required |
| -------------- | ---------------------------------------- | ---------------------------------------------- | -------- |
| type           | string                                   | Type of vector database (e.g., redis)         | Yes      |
| threshold      | float64                                  | Similarity threshold for vector search         | Yes      |
| collectionName | string                                   | Name of the collection/index                   | Yes      |
| redis          | [RedisSpec](#aigatewaycontrollerredisspec) | Redis-specific configuration                | No       |
| postgres       | [PostgresSpec](#aigatewaycontrollerpostgresspec) | PostgreSQL-specific configuration        | No       |
| qdrant         | [QdrantSpec](#aigatewaycontrollerqdrantspec)     | Qdrant-specific configuration            | No       |

### AIGatewayController.RedisSpec

| Name     | Type   | Description                    | Required |
| -------- | ------ | ------------------------------ | -------- |
| url      | string | Redis server address           | Yes      |

### AIGatewayController.PostgresSpec

| Name          | Type   | Description                    | Required |
| ------------- | ------ | ------------------------------ | -------- |
| connectionURL | string | PostgreSQL connection URL      | Yes      |

### AIGatewayController.QdrantSpec

| Name   | Type   | Description             | Required |
| ------ | ------ | ----------------------- | -------- |
| host   | string | Qdrant server host      | Yes      |
| port   | int    | Qdrant gRPC port        | Yes      |
| apiKey | string | Qdrant API key          | No       |
| useTLS | bool   | Enable TLS              | No       |

### WAFController.RuleGroupSpec
| Name        | Type                                      | Description                                           | Required |
| ----------- | ----------------------------------------- | ----------------------------------------------------- | -------- |
| name | string	| A unique name for the rule group. | Yes |
| loadOwaspCrs | bool | Indicates whether to load the OWASP Core Rule Set. For more details, please check Coraza CRS. |	No |
| rules |	[RuleSpec](#wafcontrollerrulespec) |	Defines the specific rules included in this rule group. |	Yes |

### WAFController.RuleSpec
| Name        | Type                                      | Description                                           | Required |
| ----------- | ----------------------------------------- | ----------------------------------------------------- | -------- |
| owaspRules |	[]string |	Defines the OWASP rules to be applied. See the examples at Coraza CRS for more details. |	No |
| customRules |	string |	Defines custom WAF rules.	| No |
| ipBlocker | [IPBlockerSpec](#wafcontrolleripblockerspec) |	Defines access control rules based on IP addresses (whitelist/blacklist).	| No |
| geoIPBlocker | [GeoIPBlockerSpec](#wafcontrollergeoipblockerspec) |	Defines access control rules based on geolocation (GeoIP). | No |

### WAFController.IPBlockerSpec
| Name        | Type                                      | Description                                           | Required |
| ----------- | ----------------------------------------- | ----------------------------------------------------- | -------- |
| whitelist	| []string | A list of IP addresses that are allowed access. | No |
| blacklist |	[]string |	A list of IP addresses that are denied access. | No |

### WAFController.GeoIPBlockerSpec
| Name        | Type                                      | Description                                           | Required |
| ----------- | ----------------------------------------- | ----------------------------------------------------- | -------- |
| dbPath |	string |	The file path to the GeoIP database.	| Yes |
| dbUpdateCron |	string |	A cron expression for automatically updating the GeoIP database on a schedule. |	No |
| allowedCountries |	[]string |	A list of country codes (e.g., "US", "CN") that are allowed access.	| No |
| deniedCountries |	[]string |	A list of country codes that are denied access.	| No|

---

### 07.Reference 7.02.Filters (`docs/07.Reference/7.02.Filters.md`)

# Filters 

- [Proxy](#proxy)
  - [Health Check](#health-check)
  - [Request Host](#request-host)
  - [Configuration](#configuration)
  - [Results](#results)
- [SimpleHTTPProxy](#simplehttpproxy)
  - [Configuration](#configuration-1)
  - [Results](#results-1)
- [WebSocketProxy](#websocketproxy)
  - [Health Check](#health-check-1)
  - [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)
- [RedirectorV2](#redirectorv2)
  - [Configuration](#configuration-23)
  - [Results](#results-23)
- [GRPCProxy](#grpcproxy)
  - [Configuration](#configuration-24)
  - [Results](#results-24)
- [AIGatewayProxy](#aigatewayproxy)
  - [Configuration](#configuration-25)
  - [Results](#results-25)
- [WAF](#waf)
  - [Configuration](#configuration-26)
  - [Results](#results-26)
- [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)
  - [kafka.Key](#kafkakey)
  - [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` or `http://127.0.0.1:9096` or `http://127.0.0.1:9097` based on `roundRobin`.

```yaml
kind: Proxy
name: proxy-example-1
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
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
  loadBalance:
    policy: roundRobin
maxRedirection: 10
```

### Health Check

Perform a health check on the servers in the pool. If a server fails the check, it will be marked as unhealthy, and requests will be rerouted to other healthy servers until it regains health.

```yaml
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
  healthCheck:
    # interval between health checks (default: 60s)
    interval: 60s
    # timeout for health check response (default: 3s)
    timeout: 3s
    # fail threshold to mark server as unhealthy (default: 1)
    fails: 1
    # success threshold to mark server as healthy (default: 1)
    pass: 1

    # health check request port (defaults to server's port, e.g., 9095)
    port: 10080
    # uri for health check http request
    uri: /health
    # http method for health check
    method: GET
    # http request headers for health check
    headers:
      X-Health-Check: easegress
    # http request body for health check
    body: "you-body-here"
    # username for basic authentication
    username: admin
    # password for basic authentication
    password: xxxxxx

    # response validation criteria (default: 2xx and 3xx status codes)
    match:
      # acceptable status code ranges
      statusCodes:
      - [200, 299] # 2xx
      - [300, 399] # 3xx
      # response header validation
      # name is header key.
      # value is header value, can be empty.
      # type is type of value, can be "exact" or "regexp".
      headers:
      - name: X-Status
        value: healthy
        type: exact
      # response body validation
      # type can be "contains" or "regexp".
      body:
        value: "healthy"
        type: contains
```

### Request Host

By default, if the client's request host is `example.com` and the pools.servers.url is IP-based, Easegress will forward the request to the backend with the host `example.com`. However, if `pools.servers.url` is a domain, such as `http://demo.com:9090`, Easegress will automatically update the request's host to `demo.com:9090` before sending it to the backend. To prevent this and retain the original client request host, use the `keepHost` option as shown below:

```yaml
kind: Proxy
name: proxy-example-3
pools:
- servers:
  - url: http://demo.com:9090
    keepHost: true
  loadBalance:
    policy: roundRobin
maxRedirection: 10
```

Conversely, if your `pools.servers.url` is IP-based and you prefer the backend request to use this IP, activate the `setUpstreamHost` option as illustrated below:

```yaml
kind: Proxy
name: proxy-example-4
pools:
- setUpstreamHost: true
  servers:
  - url: http://demo.com:9090
  loadBalance:
    policy: roundRobin
maxRedirection: 10
```

Note that `keepHost` takes precedence over `setUpstreamHost` because `keepHost` applies to individual servers, whereas `setUpstreamHost` affects the entire pool.

### 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](7.05.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
```

The following example demonstrates a forward proxy setup. Requests directed to 'example.com' will be forwarded, while all other requests will be denied. It's important to note that when operating a forward proxy, the paths attribute in the `HTTPServer` rule should be left empty. This is because, for HTTPS requests, the client initiates a `CONNECT` request which does not include a path.

```yaml
kind: HTTPServer
name: httpserver
port: 8088
keepAlive: true
https: false
rules:
  - host: 'example.com'
    paths:
      - backend: pipeline-forward

---

name: pipeline-forward
kind: Pipeline

filters:
- name: forward-proxy
  kind: SimpleHTTPProxy
  serverMaxBodySize: -1
```

To test this forward proxy configuration, use the following command:

```bash
https_proxy=http://127.0.0.1:8088 http_proxy=http://127.0.0.1:8088 curl https://example.com
```

### 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](7.05.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` or `ws://127.0.0.1:9096` or `ws://127.0.0.1:9097`.

```yaml
kind: WebSocketProxy
name: proxy-example-1
pools:
- servers:
  - url: ws://127.0.0.1:9095
    # keepHost: true, the `Host` will be the same as the original request
    # If the backend is a load balancer, it would prove to be highly beneficial
    keepHost: true
  - url: ws://127.0.0.1:9096
  - url: ws://127.0.0.1:9097
  loadBalance:
    policy: roundRobin
  # the max number of bytes to read for a single message in client/server connection.
  # default is 32769, set -1 to disable limit.
  clientMaxMsgSize: 32769
  serverMaxMsgSize: 32769
```

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
```

### Health Check

Perform a health check on the servers in the pool. If a server fails the check, it will be marked as unhealthy, and requests will be rerouted to other healthy servers until it regains health. Health check for websocket proxy contains both http way or websocket way. The HTTP check involves a request-response evaluation similar to a Proxy filter. In the WebSocket method, a successful connection yields a 101 status code. Additional headers can be set and evaluated in both methods.
If you send both two ways of health check, then a server passes both HTTP and WebSocket health checks, it will be considered healthy.

```
/* Detailed source-code truncated for AI context efficiency. */
```

### 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 and set header with key `Host` and value from current request host. See more details about template in [here](#template-of-builder-filters).

```yaml
kind: RequestAdaptor
name: request-adaptor-example
method: GET
header:
  del: ["X-Version"]
template: |
  method: '{{ .req.Method }}'
  header:
    set:
      Host: '{{ .req.Host }}'
```

The structure of template follows the structure of `Configuration` below.

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](7.01.Controllers.md#pipeline) it is bound.

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         | [][ratelimiter.Policy](#ratelimiterpolicy) | Policy definitions                                                                                                                                                                                                  | Yes      |
| defaultPolicyRef | string                                     | The default policy, if no `policyRef` is configured in one of the `urls`, it uses this policy                                                                                                                      | No       |
| urls             | [][urlrule.URLRule](#urlruleurlrule) | 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](7.01.Controllers.md#pipeline) it is bound.

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](../03.Advanced-Cookbook/3.07.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 <td rowspan="3">Results defined and returned by wasm code.</td> |
| ...                                                                         |
| 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"]
# sync determines the usage of AsyncProducer or SyncProducer for the Kafka filter.
# default is false. If set to true, encountering a message error will cause the Kafka filter
# to respond with a status code of 503 and a body containing "{err: "error message"}".
sync: false
topic:
  # default topic for Kafka message
  default: kafka-topic
  # dynamic topic for Kafka message, get from http header
  dynamic:
    header: X-Kafka-Topic
key:
  # default key for Kafka message
  default: kafka-key
  # dynamic key for Kafka message, get from http header
  dynamic:
    header: X-Kafka-Key
```

### Configuration

| Name         | Type     | Description                      | Required |
| ------------ | -------- | -------------------------------- | -------- |
| backend | []string | Addresses of Kafka backend | Yes      |
| sync | bool | Usage of AsyncProducer or SyncProducer, default is false | No |
| topic | [Kafka.Topic](#kafkatopic) | the topic is Spec used to get Kafka topic used to send message to the backend | Yes      |
| key | [Kafka.Key](#kafkakey) | the key is Spec used to get Kafka message key | No |

### 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](../06.Development-for-Easegress/6.2.Custom-Data.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](7.01.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 <td rowspan="3">Results defined and returned by the template .</td> |
| ...                                                                         |
| 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: <Your ClientId>
    clientSecret: <Your 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: <Your ClientId>
    clientSecret: <Your 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: <br/>`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<br/> 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 |

## RedirectorV2

The `RedirectorV2` filter provides advanced HTTP redirection capabilities within the Kubernetes API Gateway. It offers fine-grained control over URL components such as scheme, hostname, and path. The specifications are consistent with the [Kubernetes Gateway API's `HTTPRequestRedirectFilter`](https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRequestRedirectFilter).

### Configuration

| Name | Type | Description | Required |
|------|------|-------------|----------|
| scheme | string | The protocol for the redirected request (e.g., `http` or `https`). | No |
| hostname | string | The domain name to which the request should be redirected. To specify a port, include it in the hostname (e.g., `example.com:8080`). Note: Explicit port configurations are currently unsupported due to complexities in specification. | No |
| statusCode | int | The HTTP status code for the redirection response. | Yes |
| path.type | string | Determines the type of redirection. Supported values: `ReplacePrefixMatch`, `ReplaceFullPath`. | Yes |
| path.replacePrefixMatch | string | The new prefix for the redirection. Used only with `ReplacePrefixMatch`. | Conditional |
| path.replaceFullPath | string | The new full path for the redirection. Used only with `ReplaceFullPath`. | Conditional |

### Results

| Value | Description |
|-------|-------------|
| redirected | Indicates that the request has been redirected based on the `RedirectorV2` configuration. |

Commmon Use-Cases:

1. Replace Prefix:

Substitute a prefix with `/account` and return a `302 Found` status code. The prefix was decided in `pathPrefix` in te matchting rule in HTTPServer.

```yaml
name: demo-pipeline
kind: Pipeline
flow:
- filter: redirectorv2
filters:
- name: redirectorv2
  kind: RedirectorV2
  path:
    type: ReplacePrefixMatch
    replacePrefixMatch: /account
  statusCode: 302
```

2. Replace Full Path:

Redirect to an absolute path `/full-path`.

```yaml
name: demo-pipeline
kind: Pipeline
flow:
- filter: redirectorv2
filters:
- name: redirectorv2
  kind: RedirectorV2
  path:
    type: ReplaceFullPath
    replaceFullPath: /full-path
  statusCode: 302
```

3. Domain and Scheme Redirection:

Redirect to a new domain using the HTTPS protocol.

```yaml
name: demo-pipeline
kind: Pipeline
flow:
- filter: redirectorv2
filters:
- name: redirectorv2
  kind: RedirectorV2
  scheme: https
  hostname: newdomain.com
  path:
    type: ReplacePrefixMatch
    replacePrefixMatch: /account
  statusCode: 302
```

## 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
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:

- 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            |

## AIGatewayProxy

The AIGatewayProxy filter handles AI Gateway traffic by routing requests to configured AI providers through the AIGatewayController. It acts as a bridge between incoming requests and AI service providers like OpenAI, DeepSeek and so on.

The AIGatewayProxy requires an AIGatewayController to be configured with the specified provider. The filter will forward requests to the appropriate AI provider based on the providerName configuration.

Example with complete pipeline:

```yaml
name: ai-gateway-pipeline
kind: Pipeline
flow:
  - filter: ai-gateway-proxy
filters:
  - name: ai-gateway-proxy
    kind: AIGatewayProxy
    providerName: openai-provider
```

### Configuration

| Name         | Type      | Description                                                      | Required |
|--------------|-----------|------------------------------------------------------------------|----------|
| providerName | string    | Name of the AI provider configured in the AIGatewayController    | Yes      |
| middlewares  | []string  | List of middleware names to apply during request processing       | No       |

### Results

| Value                       | Description                                      |
|-----------------------------|--------------------------------------------------|
| noAIGatewayControllerError  | No AIGatewayController found or configured       |
| internalError               | Encounters an internal error during processing   |
| providerNotFound            | The specified provider is not configured         |
| middlewareError             | Error occurred in one of the configured middlewares |
| requestProcessed            | Request was successfully processed               |


## WAF

Example with complete pipeline:

```yaml
name: waf-pipeline
kind: Pipeline
filters:
- name: waf-filter
  kind: WAF
  ruleGroup: sqlinjection
- name: proxy
  kind: Proxy
  pools:
  - servers:
    - url: http://127.0.0.1:9095
    - url: http://127.0.0.1:9096
    loadBalance:
      policy: roundRobin
```

### Configuration

| Name         | Type      | Description                                                      | Required |
|--------------|-----------|------------------------------------------------------------------|----------|
| ruleGroup | string    | Name of the WAF rule configured in the WAFController    | Yes      |

### Results
| Value                       | Description                                      |
|-----------------------------|--------------------------------------------------|
| noWAFControllerError  | No WAFController found or configured       |
| ruleGroupNotFoundError               | WAF rule group not found   |
| blocked            | Request blocked by WAF rule.         |
| internalError             | Error occurred during processing the request |


## FileServer

Miniuim config example:

```yaml
name: fileserver-pipeline
kind: Pipeline
filters:
- name: fileserver-filter
  kind: FileServer
  root: "/var/www/example"
```

Complete config example:

```yaml
name: fileserver-pipeline
kind: Pipeline
filters:
- name: fileserver-filter
  kind: FileServer
  root: "/var/www/example"
  index: ["index.html", "index.txt"]  # default is index.html and index.txt
  hidden: [".git", "/build", "*/*.conf"]
  tryFiles: ["{path}", "{path}/", "/index.html", "=404 error message"]
  rewrite: "^/users/(\d+)$ /users.php?id=$1"
  precompressed: "gzip br"
  compress: "gzip"
  cache:
    bufferPoolSize: 2 * 1024 * 1024 * 1024  # default is 2GB
    bufferPoolMaxFileSize: 10 * 1024 * 1024 # default is 10MB
    bufferPoolTTL: 600                      # default is 600 seconds
    cacheFileExtensionFilters: 
    - pattern: ["*.html", "*.css"]
      etagMaxAge: 3600                      # default is 3600
    - pattern: ["/usr/*.js"]
```


### Configuration


| Name | Type | Description | Required |
| --- | --- | --- | --- |
| root | string | The root directory path from which to serve files. | Yes |
| index | []string | A list of filenames to look for, in order, when a directory is requested. Defaults to `["index.html", "index.txt"]`. | No |
| hidden | []string | A list of glob patterns for files or directories to hide. Requests for these files will be treated as if they do not exist (404 Not Found). | No |
| tryFiles | []string | A sequence of files to try serving in order. If the previous file is not found, the next one is tried. Useful for Single-Page Applications (SPAs) or front-controller patterns. Supports placeholders like `{path}` and can end with an error code like `=404`. | No |
| rewrite | string | A rewrite rule used to internally modify the request URI before file lookups. It uses regular expression matching and capture groups (e.g., `$1`). | No |
| precompressed | string | A space-separated list of precompressed formats (e.g., `gzip br`). For a request to `/file`, it will check for the existence of `/file.gz`, `/file.br`, etc. | No |
| compress | string | The compression method (e.g., `gzip`) to use for on-the-fly compression of responses that are not precompressed. | No |
| cache | [CacheSpec](#fileservercachespec) | A container for configuring caching-related settings. | No |

#### More about hidden rules
##### 1. Component Patterns (rules without a path separator)
These patterns (e.g., ".git", "*.log") are matched against each individual name component of the relative path.
```
- Rule: ".git"
- Hides: "/path/to/project/.git", "/path/to/.git/config"
- Does NOT hide: "/path/to/project/git"

- Rule: "node_modules"
- Hides: "/path/to/project/node_modules", "/path/to/node_modules/express"
```

##### 2. Path Patterns (rules with a path separator):
These patterns are matched against the full absolute path. This matching is done in two ways:

```
a) As a prefix: If the rule is a prefix of the path, followed by a path separator, it's a match.
- Rule: "/build"
- Hides: "/build/app.js", "/build/"
- Does NOT hide: "/builder/app.js"

b) As a glob pattern: The rule is treated as a glob pattern to be matched against the entire absolute path.
- Rule: "/*/*/*.conf"
- Hides: "/etc/nginx/nginx.conf"
- Does NOT hide: "/etc/nginx.conf"
```

### Results

| Value | Description |
| --- | --- |
| internalError | An internal error occurred within the FileServer system |
| serverError | A server-side error occurred |
| clientError | The client's request was blocked |
| notFound | A required resource could not be found |

## 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](7.05.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 |
| healthCheck | ProxyHealthCheckSpec | Health check. Full example with details in [Proxy Health Check](#health-check) | No |
| setUpstreamHost | bool | Set request host to the host of backend server url if true. Default is false. | 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.cn`, `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`, `cookieHash` 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) | (Deprecated) Use [Proxy](#health-check) or [WebSocketProxy](#health-check-1) instead. | 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

(Deprecated) Use [Proxy](#health-check) or [WebSocketProxy](#health-check-1) instead.

| 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       |
| healthCheck | WSProxyHealthCheckSpec | Health check for Websocket. Full example with details in [WebSocketProxy Health Check](#health-check-1) | 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      |

### kafka.Key

| Name      | Type   | Description                                                              | Required |
| --------- | ------ | ------------------------------------------------------------------------ | -------- |
| default | string | Default key for Kafka message | Yes      |
| dynamic.header | string | The HTTP header that contains Kafka key | No      |

### 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.
- **urlQueryEscape**: escapes the string so that it can be safely placed inside
  a URL query, equivalent to the `urlquery` template function.
- **urlQueryUnescape**: performs the inverse transformation of `urlQueryEscape`,
  decoding a URL-encoded string back to its origin form.
- **host**: host splits a network address of the form "host:port" and return host part by using `net.SplitHostPort`.
- **port**: port splits a network address of the form "host:port" and return port part by using `net.SplitHostPort`.

Easegress injects existing requests/responses of the current context into
the template engine at runtime, so we can use `.requests.<namespace>.<field>`
or `.responses.<namespace>.<field>` to read the information out (the
available fields vary from the protocol of the request or response,
and please refer [Pipeline](7.01.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`.

For convenience, shorthand notations are used to simplify the access of default request and response properties. In this notation:

- `.req.Host` is equal to `.requests.DEFAULT.Host`
- `.resp.Body` is equal to `.responses.DEFAULT.Body`

Here, `.req` is a shorthand for `.requests.DEFAULT`, and similarly, `.resp` is shorthand for `.requests.DEFAULT`.

Easegress also injects other data into the template engine, which can be
accessed with `.data.<name>`, 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.

Use `RequestAdaptor` as an example:

```yaml
kind: RequestAdaptor
name: request-adaptor
template: |
  header:
      set:
          Content: '{{ header .req.Header "Content-Length" }}'
          Content-Type: '{{ header .req.Header "Content-Type" }}'
          Host: '{{ .req.Host }}'
          Method: '{{ .req.Method }}'
          Remote-Addr: '{{ .req.RemoteAddr }}'
          Remote-User: '{{ username .req }}'
          Request-Body: '{{ .req.Body }}'
          Request-URI: '{{ .req.RequestURI }}'
          Scheme: '{{ .req.URL.Scheme }}'
```

#### 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       |



### fileserver.CacheSpec

This configuration is nested under the `cache` field.

| Name | Type | Description | Required |
| --- | --- | --- | --- |
| bufferPoolSize | integer | The maximum size of the in-memory cache pool in bytes. Defaults to `2147483648` (2 GB). | No |
| bufferPoolMaxFileSize | integer | The maximum size in bytes that a single file can be to be cached in the buffer pool. Defaults to `10485760` (10 MB). | No |
| bufferPoolTTL | integer | The time-to-live (TTL) for items in the cache pool, in seconds. Defaults to `600`. | No |
| cacheFileExtensionFilters | [][FileExtensionFilter](#fileserverfileextensionfilter) | A list of rules to control `ETag` cache headers based on file patterns. | No |

### fileserver.FileExtensionFilter

This configuration is for each object within the `cacheFileExtensionFilters` array.

| Name | Type | Description | Required |
| --- | --- | --- | --- |
| pattern | []string | A list of glob patterns to match files against (e.g., `["*.html", "*.css"]`). | Yes |
| etagMaxAge | integer | The `max-age` value, in seconds, to set for the `ETag` response header for files matching this pattern. Defaults to `3600`. | No |

---

### 07.Reference 7.03.Ingress Controller (`docs/07.Reference/7.03.Ingress-Controller.md`)

# IngressController 

- [Prerequisites](#prerequisites)
- [Configuration](#configuration)
  - [Controller spec](#controller-spec)
- [Getting Started](#getting-started)
  - [Role Based Access Control configuration](#role-based-access-control-configuration)
  - [Configurations to ConfigMap](#configurations-to-configmap)
  - [Deploy Easegress IngressController](#deploy-easegress-ingresscontroller)
  - [Create backend service \& Kubernetes ingress](#create-backend-service--kubernetes-ingress)
- [Multi-instance IngressController](#multi-instance-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.

## Prerequisites

1. K8s cluster : **v1.18+**

## Configuration

### Controller spec

```yaml
kind: IngressController
name: ingress-controller-example
kubeConfig:
masterURL:
namespaces: ["default"]
ingressClass: easegress
httpServer:
  port: 8080
  https: false
  keepAlive: true
  keepAliveTimeout: 60s
  maxConnections: 10240
```

- 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.

- The `namespaces` is an array of Kubernetes namespaces which the IngressController needs to watch, all namespaces are watched if left empty.
- IngressController only handles `Ingresses` with `ingressClassName` set to `ingressClass`, the default value of `ingressClass` is `easegress`.
- One IngressController manages a shared HTTP traffic gate and multiple pipelines according to the Kubernetes ingress. The `httpServer` section in the spec is the basic configuration for the shared HTTP traffic gate. The routing part of the HTTP server and pipeline configurations will be generated dynamically according to Kubernetes ingresses.

## Getting Started

### Role Based Access Control configuration

If your cluster is configured with RBAC, first you will need to authorize Easegress IngressController for using the Kubernetes API. Below is an example configuration:

```yaml
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: easegress-ingress-controller
rules:
- apiGroups: [""] # "" indicates the core API group
  resources: ["services", "endpoints", "secrets"]
  verbs: ["get", "watch", "list"]
- apiGroups: ["networking.k8s.io"]
  resources: ["ingresses"]
  verbs: ["get", "watch", "list"]

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: easegress-ingress-controller
  namespace: default

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: easegress-ingress-controller
subjects:
- kind: ServiceAccount
  name: easegress-ingress-controller
  namespace: default
roleRef:
  kind: ClusterRole
  name: easegress-ingress-controller
  apiGroup: rbac.authorization.k8s.io
```

Note the name of the ServiceAccount we just created is `easegress-ingress-controller`, it will be used later.

### Configurations to ConfigMap

Let's use ConfigMap to store Easegress server configuration and Easegress ingress configuration. This ConfigMap is used later in Deployment.

```yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: easegress-cm
  namespace: default
data:
  easegress-server.yaml: |
    name: ingress-easegress
    cluster-name: easegress-ingress-controller
    cluster-role: primary
    api-addr: 0.0.0.0:2381
    data-dir: /opt/easegress/data
    log-dir: /opt/easegress/log
    debug: false
  controller.yaml: |
    kind: IngressController
    name: ingress-controller-example
    kubeConfig:
    masterURL:
    namespaces: ["default"]
    ingressClass: easegress
    httpServer:
      port: 8080
      https: false
      keepAlive: true
      keepAliveTimeout: 60s
      maxConnections: 10240
```

The `easegress-server.yaml` creates Easegress instance named *easegress-ingress-controller* and `controller.yaml` defines IngressController object for Easegress.

### Deploy Easegress IngressController

To deploy the IngressController, we will create a Deployment and a Service as below:

```
/* Detailed source-code truncated for AI context efficiency. */
```

The IngressController is created via the command line argument `initial-object-config-files` of `easegress-server`. Notice how Easegress logs and data are stored to *emptyDir* called `ingress-data-volume` inside the pod. IngressController is stateless so we can restart new pods without preserving previous state.

Last but not least let's create service for forwarding the Ingress traffic to Easegress.

```yaml
apiVersion: v1
kind: Service
metadata:
  name: easegress-public
  namespace: default
spec:
  ports:
  - name: web
    protocol: TCP
    port: 8080
    nodePort: 30080
  selector:
    app: easegress-ingress
  type: NodePort
```

The port `web` is to receive external HTTP requests from port 30080 and forward them to the HTTP server in Easegress.

### Create backend service & Kubernetes ingress

Apply below YAML configuration to Kubernetes:

```yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-deployment
spec:
  selector:
    matchLabels:
      app: products
      department: sales
  replicas: 2
  template:
    metadata:
      labels:
        app: products
        department: sales
    spec:
      containers:
      - name: hello-v1
        image: "us-docker.pkg.dev/google-samples/containers/gke/hello-app:1.0"
        env:
        - name: "PORT"
          value: "50001"
      - name: hello-v2
        image: "us-docker.pkg.dev/google-samples/containers/gke/hello-app:2.0"
        env:
        - name: "PORT"
          value: "50002"

---
apiVersion: v1
kind: Service
metadata:
  name: hello-service
spec:
  type: NodePort
  selector:
    app: products
    department: sales
  ports:
  - name: port-v1
    protocol: TCP
    port: 60001
    targetPort: 50001
  - name: port-v2
    protocol: TCP
    port: 60002
    targetPort: 50002

---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-example
spec:
  ingressClassName: easegress
  rules:
  - host: "www.example.com"
    http:
      paths:
      - pathType: Prefix
        path: /
        backend:
          service:
            name: hello-service
            port:
              number: 60001
  - host: "*.megaease.com"
    http:
      paths:
      - pathType: Prefix
        path: /
        backend:
          service:
            name: hello-service
            port:
              number: 60002
```

Once all pods are up and running, we can leverage the command below to access both versions of the `hello` application:

```bash
$ curl http://{NODE_IP}:30080/ -HHost:www.megaease.com
Hello, world!
Version: 2.0.0
Hostname: hello-deployment-6cbf765985-r6242

$ curl http://{NODE_IP}:30080/ -HHost:www.example.com
Hello, world!
Version: 1.0.0
Hostname: hello-deployment-6cbf765985-r6242
```

And we can see Easegress IngressController has forwarded requests to the correct application version according to Kubernetes ingress.

## Multi-instance IngressController

In previous chapters we created IngressController with one instance running. To support high-availability scenarios, you can increase the number of replicas in the Deployment:

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: easegress-ingress
  name: easegress
  namespace: default
spec:
  replicas: 2 # number of IngressController instances running
  ...
```

---

### 07.Reference 7.04.FaaSController (`docs/07.Reference/7.04.FaaSController.md`)

# 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 [here](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   <pending>     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.

```
/* Detailed source-code truncated for AI context efficiency. */
```

| 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/

---

### 07.Reference 7.05.Stream (`docs/07.Reference/7.05.Stream.md`)

# Stream

Most Easegress traffic is message-based, but Easegress v2 pipeline is
protocol independent, that's it supports stream-based traffic like TCP, and
there's stream traffic even in HTTP.

Another new feature in Easegress v2 is multiple requests/responses support,
this requires the payload of a request/response can be read more than once,
for message-based traffic, this is simple and easy to do, as Easegress can
read the full message payload into memory. But for stream-based traffic,
this is impossible, as the payload may require too much memory, and/or take
too much time to read it into memory.

To resolve the above issue, Easegress allow user or developer to configure
whether a request/response is a stream, for example:

* We can set `clientMaxBodySize` of an HTTP server to a negative value to
  tell Easegress the request is a stream, and not a stream otherwise. Please
  refer [HTTPServer](7.01.Controllers.md#httpserver) for more information.
* We can set `serverMaxBodySize` of a `Proxy` filter to a negative value to
  tell Easegress the response is a stream, and not a stream otherwise. Please
  refer [Proxy](7.02.Filters.md#proxy) for more information.

As we have mentioned above, the payload of a stream-based request/response
can only be read once, so some features are not possible for these
requests/responses, including:

* In the `template` of `RequestBuilder` or `ResponseBuilder`, you cannot
  access the payload(in HTTP, the body) of an existing stream-based
  request/response, while it is fine to access other information of the
  request/response.
* Stream-based request/response cannot be cached by `Proxy`.
* The `HeaderToJSON` filter does not support stream-based requests/responses.
* You cannot access the payload of stream-based request/response in a
  `WasmHost` filter.

---

### 07.Reference 7.06.Routers (`docs/07.Reference/7.06.Routers.md`)

# Routers 

- [Ordered](#ordered)
- [RadixTree](#radixtree)
  - [1. Full match](#1-full-match)
  - [2. Prefix match](#2-prefix-match)
  - [3. Parameter match](#3-parameter-match)
  - [4. Regexp match](#4-regexp-match)
  - [5. Match priority](#5-match-priority)
  - [Rewrite](#rewrite)

Router determines how requests are routed to the corresponding Pipeline for subsequent processing. We currently support two routing strategies, `Ordered` and `RadixTree`, and you can choose a Router that suits your needs based on its characteristics, and we also provide the ability to customize Router, if the built-in Router does not meet your needs, you can choose Write a custom Router.

## Ordered

Ordered router is the default router for httpserver, as the name implies, its matching rules are in the order of the route definition.

Let's look at some examples to better understand the strategy.

```yaml
kind: HTTPServer
name: server-demo
port: 10080
keepAlive: true
https: false
routerKind: Ordered
rules:
  - paths:
    - path: /pipeline/abc
      backend: static-backend
    - pathRegexp: `^/pipeline/regexp$`
      backend: regexp-backend
    - pathPrefix: /pipeline
      backend: prefix-backend
```

| path | Match backend |
|------|--------------|
| /pipeline/abc | `static-backend` |
|/pipeline/regexp | `regexp-backend` |
|/pipeline/test | `prefix-backend` |
|/pipeline/test | `prefix-backend` |
|/blog/bar | not match |

Take a look at another example.

```yaml
kind: HTTPServer
name: server-demo
port: 10080
keepAlive: true
https: false
routerKind: Ordered
rules:
  - paths:
    - pathPrefix: /pipeline
      backend: prefix-backend
    - path: /pipeline/abc
      backend: static-backend
    - pathRegexp: `^/pipeline/regexp$`
      backend: regexp-backend
```

| path | Match backend |
|------|--------------|
|/pipeline/abc | `prefix-backend` |
|/pipeline/regexp | `prefix-backend` |
|/pipeline/test | `prefix-backend` |
|/pipeline/test | `prefix-backend` |
|/blog/bar | not match |

It is clear to see that the matching rules of the router are matched in the order of route definition, and the matching stops when the result is reached.

## RadixTree

As the name implies, you can see that the underlying mechanism of the router uses a [Radix tree](https://en.wikipedia.org/wiki/Radix_tree)

Still in the same way, let's use some examples to better understand the strategy.

### 1. Full match

```shell
/users/aniaan
```

This rule will only match `/users/aniaan`

### 2. Prefix match

```shell
/users/*
```

This rule will match all paths prefixed with `/users`, eg: `/users/aniaan`, `/users/localvar`, `/users/megaease/abc`

### 3. Parameter match

```shell
/users/{username}/hovercard
```

This rule will match `/users/aniaan/hovercard` and `/users/localvar/hovercard`

### 4. Regexp match

```shell
/users/{username:[a-z]+}/hovercard
```

This rule will match `/users/aniaan/hovercard` and `/users/localvar/hovercard`, not `/users/123/hovercard`.

### 5. Match priority

`Full match` -> `Regexp match` -> `Parameter match` -> `Prefix match`

Let's look at an example to get a better impression.

```yaml
kind: HTTPServer
name: server-demo
port: 10080
keepAlive: true
https: false
routerKind: RadixTree
rules:
  - paths:
    - path: /users/{username}/hovercard
      backend: parameter-path-backend
    - path: /users/{username:[a-z]+}/hovercard
      backend: regexp-backend
    - path: /users/*
      backend: prefix-backend
    - path: /users/aniaan/hovercard
      backend: full-match-backend
```

| path | Match backend |
|------|--------------|
|/users/aniaan/hovercard | `full-match-backend` |
|/users/12345/hovercard | `parameter-path-backend` |
|/users/localvar/hovercard | `regexp-backend` |
|/users/test | `prefix-backend` |
|/blog/bar | not match |

Let's take a look at another example to deepen our understanding

```yaml
kind: HTTPServer
name: server-demo
port: 10080
keepAlive: true
https: false
routerKind: RadixTree
rules:
  - paths:
    - path: /user/{username}/123
      backend: h1
    - path: /user/wang/{num}
      backend: h2
    - path: /{type}/wang/123
      backend: h3
```

`/user/wang/123` will match `/user/wang/{num}`, which is determined by the matching order of RadixTree, you can split the path into individual characters, each character follows the matching order of `Full match` -> `Regexp match` -> `Parameter match` -> `Prefix match`, so the final match result is `/user/wang/{num}`.

RadixTree, compared with Ordered, has lost its definition order.

See one more.

```yaml
kind: HTTPServer
name: server-demo
port: 10080
keepAlive: true
https: false
routerKind: RadixTree
- paths:
    - path: /users/{u:[a-z]+}sername/hovercard
      backend: h1
    - path: /users/u{sername}/hovercard
      backend: h2
```

Similarly, `/users/username/hovercard` will match `/users/u{sername}/hovercard` for the same reason as above, so you can try to understand it.

### Rewrite

Let's take a look at how RadixTree's Path Rewrite feature works. The syntax of Rewrite remains basically the same as that of Path, with a few minor differences.

```yaml
kind: HTTPServer
name: server-demo
port: 10080
keepAlive: true
https: false
routerKind: RadixTree
rules:
  - paths:
    - path: /users/{username}/hovercard
      rewriteTarget: /api/users/{username}/card
      backend: parameter-path-backend
    - path: /users/{username:[a-z]+}/hovercard
      rewriteTarget: /api/users/{username}/card
      backend: regexp-backend
    - path: /users/*
      rewriteTarget: /api/users/{EG_WILDCARD}
      backend: prefix-backend
    - path: /users/aniaan/hovercard
      rewriteTarget: /api/users/aniaan/card
      backend: full-match-backend
```

| path | Match backend | Rewrite target|
|------|--------------|----------------|
|/users/aniaan/hovercard | `full-match-backend` | /api/users/aniaan/card |
|/users/12345/hovercard | `parameter-path-backend` | /api/users/12345/card |
|/users/localvar/hovercard | `regexp-backend` |  /api/users/localvar/card |
|/users/test | `prefix-backend` | /api/users/test |

You can see that only the wildcard `*` needs to be replaced by the built-in variable `EG_WILDCARD`.

---

### 07.Reference 7.08.Metrics (`docs/07.Reference/7.08.Metrics.md`)

# Prometheus 


- [Exporter URI](#exporter-uri)
- [Metrics](#metrics)
  - [HTTPServer](#httpserver)
  - [Proxy Filter](#proxy-filter)
- [Create Metrics for Extended Resources and Filters](#create-metrics-for-extended-resources-and-filters)

Easegress has a builtin Prometheus exporter.

## Exporter URI

```
Get /apis/v2/metrics
```

## Metrics

### HTTPServer

| Metric                                     | Type      | Description                                                  | Labels                                                                  |
|--------------------------------------------|-----------|--------------------------------------------------------------|-------------------------------------------------------------------------|
| httpserver_health                          | gauge     | show the status for the http server: 1 for ready, 0 for down | clusterName, clusterRole, instanceName, name, kind                      |
| httpserver_total_requests                  | counter   | the total count of http requests                             | clusterName, clusterRole, instanceName, name, kind, routerKind, backend |
| httpserver_total_responses                 | counter   | the total count of http resposnes                            | clusterName, clusterRole, instanceName, name, kind, routerKind, backend |
| httpserver_total_error_requests            | counter   | the total count of http error requests                       | clusterName, clusterRole, instanceName, name, kind, routerKind, backend |
| httpserver_requests_duration               | histogram | request processing duration histogram                        | clusterName, clusterRole, instanceName, name, kind, routerKind, backend |
| httpserver_requests_size_bytes             | histogram | a histogram of the total size of the request. Includes body  | clusterName, clusterRole, instanceName, name, kind, routerKind, backend |
| httpserver_responses_size_bytes            | histogram | a histogram of the total size of the returned responses body | clusterName, clusterRole, instanceName, name, kind, routerKind, backend |
| httpserver_requests_duration_percentage    | summary   | request processing duration summary                          | clusterName, clusterRole, instanceName, name, kind, routerKind, backend |
| httpserver_requests_size_bytes_percentage  | summary   | a summary of the total size of the request. Includes body    | clusterName, clusterRole, instanceName, name, kind, routerKind, backend |
| httpserver_responses_size_bytes_percentage | summary   | a summary of the total size of the returned responses body   | clusterName, clusterRole, instanceName, name, kind, routerKind, backend |


### Proxy Filter

| Metric                              | Type      | Description                                   | Labels                                                                              |
|-------------------------------------|-----------|-----------------------------------------------|-------------------------------------------------------------------------------------|
| proxy_total_connections             | counter   | the total count of proxy connections          | clusterName, clusterRole, instanceName, name, kind, loadBalancePolicy, filterPolicy |
| proxy_total_error_connections       | counter   | the total count of proxy error connections    | clusterName, clusterRole, instanceName, name, kind, loadBalancePolicy, filterPolicy |
| proxy_request_body_size             | histogram | a histogram of the total size of the request  | clusterName, clusterRole, instanceName, name, kind, loadBalancePolicy, filterPolicy |
| proxy_response_body_size            | histogram | a histogram of the total size of the response | clusterName, clusterRole, instanceName, name, kind, loadBalancePolicy, filterPolicy |
| proxy_request_body_size_percentage  | summary   | a summary of the total size of the request    | clusterName, clusterRole, instanceName, name, kind, loadBalancePolicy, filterPolicy |
| proxy_response_body_size_percentage | summary   | a summary of the total size of the response   | clusterName, clusterRole, instanceName, name, kind, loadBalancePolicy, filterPolicy |

## Create Metrics for Extended Resources and Filters

We provide several helper functions to help you create Prometheus metrics
when develop your own Resources and Filters.

* `prometheushelper.NewCounter`
* `prometheushelper.NewGauge`
* `prometheushelper.NewHistogram`
* `prometheushelper.NewSummary`

These metrics will be registered to `DefaultRegisterer` automatically.

Besides, you can use native Prometheus SDK to create metrics and register them
by yourself.

---

### 07.Reference 7.09.Maintain (`docs/07.Reference/7.09.Maintain.md`)

# Release steps

These are the steps to release a new version of Easegress.

## Pre-requisites

Let's suppose that the next version is `X.Y.Z`. Then new *tag* is `vX.Y.Z` (note prefix `v`) and new *release title* is `easegress-vX.Y.Z`. Please collect all Pull Request's that will be mentioned in `CHANGELOG.md` and in the Release Note.

## Steps

1. Create a Pull Request that updates release version `X.Y.Z` in Makefile and adds the new version to `CHANGELOG.md`. Include significant changes, implemented enchantments and fixed bugs to `CHANGELOG.md`. Once the PR is reviewed and approved, merge it to main branch.
2. Create new release note at https://github.com/megaease/easegress/releases/new with *release title* `easegress-vX.Y.Z`, click *Choose a tag* and add new tag `vX.Y.Z`. Write the Release note. You can use the modifications written to `CHANGELOG.md` in previous step. Follow the same format, as previous Release notes. Once written, click `Save draft`. 
3. New tag triggers Goreleaser Github action. Follow up that it creates and uploads successfully all binaries and Docker images. 
4. Ensure that the release note created in step 2 contains the command to pull the new Docker image.
5. Edit the release note create in step 2, and click `Publish release`. 
6. New version of Easegress is now released! Notify everyone to check it out!

---

### 06.Development For Easegress 6.1.Developer Guide (`docs/06.Development-for-Easegress/6.1.Developer-Guide.md`)

# Developer Guide 

- [Architecture](#architecture)
- [Project Layout](#project-layout)
- [Building and testing](#building-and-testing)
- [Extending Easegress](#extending-easegress)
  - [egbuilder](#egbuilder)
  - [Developing an Object](#developing-an-object)
    - [Main Business Logic](#main-business-logic)
    - [Register Object to Supervisor](#register-object-to-supervisor)
  - [Developing a Filter](#developing-a-filter)
    - [Main Business Logic](#main-business-logic-1)
    - [Register Filter to Pipeline](#register-filter-to-pipeline)
    - [JumpIf Mechanism in Pipeline](#jumpif-mechanism-in-pipeline)

## Architecture

The following diagram describes the high-level architecture of Easegress. The Cluster component is responsible for synchronizing data among different nodes or after restarts. The Supervisor manages the lifecycle of all kinds of objects.



Easegress has four different *kind* of objects:
1. **System Controllers** operate essential system-level activities. Every instance of Easegress launches exactly one instance of each kind of System Controllers after the very start.
2. **Business Controllers** can be created and deleted using admin operations. Business Controllers operate mainly on other tasks than handling directly the traffic.
3. **Traffic Gate** receives traffic of different protocols, and dispatches them to pipelines.
4. **Pipeline** is a filter chain that handles traffic from the traffic gate.


## Project Layout

Easegress code structure follows the [go project layout standard](https://github.com/golang-standards/project-layout), and the important directories are described below:

```
.
├── bin                   // executable binary
├── cmd                   // command source code
├── docs                  // documents
├── pkg                   // importable golang packages
│   ├── api               // restful api layer
│   ├── cluster           // cluster component
│   ├── common            // some common utilies
│   ├── context           // context for traffic gate and pipeline
│   ├── env               // preparation for running environment
│   ├── filters           // filters
│   ├── graceupdate       // graceful update
│   ├── logger            // logger utilities
│   ├── object            // controllers
│   ├── option            // startup arguments utilities
│   ├── pidfile           // handle file to record pid
│   ├── profile           // dedicated pprof
│   ├── protocols         // decoupling for protocol
│   ├── registry          // registry for all dynamic registering component
│   ├── resilience        // resilience handling
│   ├── supervisor        // the supervisor to manage controllers
│   ├── tracing           // distributed tracing
│   ├── util              // all kinds of utilities
│   ├── v                 // validation tool
│   └── version           // release version
├── test                  // scripts of integration testing
```

## Building and testing

If you are new to Easegress, please go through the  [Getting Started](../01.Getting-Started/1.1.Quick-Start.md) tutorial,  if you have not already. Also try out different [Tutorials](../02.Tutorials/README.md) tutorials.

To build `easegress-server` and `egctl`, run `make` and they will appear to `bin` directory.

After modifying the code, run `make fmt` to format the code and `make test` to run unit tests.

## Extending Easegress

Let's suppose that you have a requirement or a feature enhancement in your mind. The most common way to extend Easegress is to develop a new Object or Filter. 

### egbuilder
`egbuilder` provides tools to quickly create, build, and run Easegress with custom plugins. For more information, please check [here](6.3.egbuilder.md).

### Developing an Object

The first thing is to choose which one of the four Object types is the most suitable for a given feature. In most cases, creating a new Business Controller is the best choice to extend the ability of Easegress at the object level. So let's develop a lightweight business controller to show the details. For example, let's say you want to develop a controller called `StatusInLocalController` that dumps the status of all objects to a local file. Here's the config of the controller:

```yaml
kind: StatusInLocalController
name: statusInLocal
path: ./running_status.yaml
```

#### Main Business Logic

Put the controller package in `pkg/object/statusinlocalcontroller` and implement the main logic in `pkg/object/statusinlocalcontroller/statusinlocalcontroller.go`:

The supervisor has all references of running objects, so you need invoke supervisor to get status of running objects, and the main business code would be:

```go
type (
	// StatusInLocalController posts status of all objects in a local file.
	StatusInLocalController struct {
		superSpec *supervisor.Spec
		spec      *Spec

		done chan struct{}
	}

	// Spec describes StatusInLocalController.
	Spec struct {
		Path string `yaml:"path" jsonschema:"required"`
	}

	// Entry is the structure of the status file
	Entry struct {
		Statuses     map[string]interface{}
		UnixTimestamp int64
	}
)

func (c *StatusInLocalController) syncStatus() {
	// Step1: Use entry to record status of all running objects.
	entry := &Entry{
		UnixTimestamp: time.Now().Unix(),
		Statuses:      make(map[string]interface{}),
	}

	walkFn := func(entity *supervisor.ObjectEntity) bool {
		defer func() {
			if err := recover(); err != nil {
				logger.Errorf("recover from syncStatus, err: %v, stack trace:\n%s\n",
					err, debug.Stack())
			}
		}()

		name := entity.Spec().Name()
		entry.Statuses[name] = entity.Instance().Status()

		return true
	}

	c.superSpec.Super().WalkControllers(walkFn)

	// Step2: Write the status to the local file.
	buff, err := yaml.Marshal(entry)
	if err != nil {
		logger.Errorf("BUG: marshal %#v to yaml failed: %v",
			entry, err)
		return
	}

	os.WriteFile(c.spec.Path, buff, 0644)
}
```

#### Register Object to Supervisor

All objects must satisfy the interface `Object` in [`pkg/object/supervisor/registry.go`](https://github.com/megaease/easegress/blob/master/pkg/supervisor/registry.go).

```
/* Detailed source-code truncated for AI context efficiency. */
```

In the end, we have to import `StatusInLocalController` in `pkg/registry/registry.go`.
```go
import (
// Filters
// ...

// Objects
// ...
	_ "github.com/megaease/easegress/v2/pkg/object/statusinlocalcontroller"
)

```

### Developing a Filter

In most scenarios of handling traffic, creating a new Filter is the right choice, since its scheduling is covered by the flexible Pipeline. Filters are executed in a Pipeline sequentially, each one being responsible for one step of the traffic handling. For example, let's develop a filter to count the number of requests which have the specified header. Let's name the kind of filter `headerCounter`, so the config of the filter in pipeline spec would be:

```yaml
filters:
- kind: HeaderCounter
  name: headerCounter
  headers: ['Cookie', 'Authorization']
```

#### Main Business Logic

Put the filter package in `pkg/filters/headercounter`. You could implement the main logic counting the header in `pkg/filters/headercounter/headercounter.go`:

```go

type (
	HeaderCounter struct {
		spec     *Spec						// The filter spec in its own level.

		// The read and write for count must be locked, because the Handle is called concurrently.
		countMutex sync.Mutex
		count      map[string]int64
	}

	Spec struct {
		Headers []string `yaml:"headers"`
	}
)

func (m *HeaderCounter) Handle(ctx *context.Context) (result string) {
	for _, key := range m.spec.Headers {
		// suppose this filter can only handle HTTP requests.
		value := ctx.InputRequest().(*httpprot.Request).HTTPHeader().Get(key)
		if value != "" {
			m.countMutex.Lock()
			m.count[key]++
			m.countMutex.Unlock()
		}
	}
	return ""
}
```

`HeaderCounter` struct contains a field of `*Spec`, mainly for configuring the filter. Fields `countMutex` and `count` are specific to this filter; the `Handle` function uses them to count the headers.

#### Register Filter to Pipeline

Our core logic is very simple, now let's add some non-business code to make our new filter conform to the requirement of the Pipeline framework. All filters must satisfy the interface `Filter` in [`pkg/object/filters/filters.go`](https://github.com/megaease/easegress/blob/main/pkg/filters/filters.go).

All of the methods with their names and comments are clean, the only one we need to emphasize is `Inherit`. It is called when the pipeline is updated, without modifying the filter's identity (*name* and *kind*). In practice, this happens when the underlying machine reboots and restarts Easegress. It's the filter's own responsibility to do hot-update in `Inherit` such as transferring meaningful consecutive data.

```go
const (
	// Kind is the kind of HeaderCounter.
	HeaderCounterKind = "HeaderCounter"
)

var kind = &filters.Kind{
	Name:        HeaderCounterKind,
	Description: "HeaderCounter counts the number of requests which contain the specified header.",
	Results:     []string{},
	DefaultSpec: func() filters.Spec { return &Spec{} },
	CreateInstance: func(spec filters.Spec) filters.Filter {
		return &HeaderCounter{spec: spec.(*Spec)}
	},
}

func init() { filters.Register(kind) }

// Name returns the name of the HeaderCounter filter instance.
func (hc *HeaderCounter) Name() string { return hc.spec.Name() }

// Kind returns the kind of HeaderCounter.
func (hc *HeaderCounter) Kind() *filters.Kind { return kind }

// Spec returns the spec used by the HeaderCounter.
func (hc *HeaderCounter) Spec() filters.Spec { return hc.spec }

// Init initializes HeaderCounter.
func (hc *HeaderCounter) Init() { hc.reload() }

// Inherit inherits previous generation of HeaderCounter.
func (hc *HeaderCounter) Inherit(previousGeneration filters.Filter) {
	hc.Init(pipeSpec)
}

func (m *HeaderCounter) reload() {
	m.count = make(map[string]int64)
}

// Status returns status.
func (m *HeaderCounter) Status() interface{} { return nil }

// Close closes HeaderCounter.
func (m *HeaderCounter) Close() {}
```

Then we need to add the import line in the `pkg/registry/registry.go`:

```go
import (
	_ "github.com/megaease/easegress/v2/pkg/filters/headercounter
)
```

#### JumpIf Mechanism in Pipeline

The [Getting Started](../README.md#getting-started) part of the README uses briefly the `jumpIf` mechanism of the `Pipeline`. Let's describe the concept of `jumpIf` using the example below:

```yaml
name: pipeline-demo
kind: Pipeline
flow:
- filter: validator
  jumpIf: { invalid: END }
- filter: requestAdaptor
- filter: proxy
```

That `jumpIf` means the flow will jump to the end of the `Pipeline` without going through `requestAdaptor` and `proxy` if the `validator` returns the result `invalid`. On the other hand, when `validator` returns an empty string `""` the request proceeds to the next filter as usual.

So the purpose of the field `Results` in the `filters.Kind` type is to register all possible results of the filter. In the example of `HeaderCounter`, the empty results mean `Handle` only returns the empty result. In order to use `jumpIf` to skip proceeding filters, `Results` has to define the output code for invalid execution. So if we want to prevent requests which haven't any counting headers from going forward to the next filters, we could change it to:

```go
const resultInvalidHeader = "invalidHeader"

var kind = &filters.Kind{
	Name:        HeaderCounterKind,
	Description: "HeaderCounter counts the number of requests which contain the specified header.",
	Results:     []string{resultInvalidHeader}, // add 'invalidHeader' to result.
	DefaultSpec: func() filters.Spec { return &Spec{} },
	CreateInstance: func(spec filters.Spec) filters.Filter {
		return &HeaderCounter{spec: spec.(*Spec)}
	},
}
```

And we will also need to revise the `Handle` method of the `HeaderCounter` filter:

```go
// Handle counts the header of the requests.
func (hc *HeaderCounter) Handle(ctx *context.Context) (result string) {
	counted := false // New code

	for _, key := range hc.spec.Headers {
		value := ctx.InputRequest().(*httpprot.Request).HTTPHeader().Get(key)
		if value != "" {
			m.countMutex.Lock()
			counted = true // New code
			m.count[key]++
			m.countMutex.Unlock()
		}
	}

	// New code, skip the rest of the pipeline if no header is counted.
	if !counted {
		return resultInvalidHeader
	}

	return ""
}
```

---

### 06.Development For Easegress 6.2.Custom Data (`docs/06.Development-for-Easegress/6.2.Custom-Data.md`)

# Custom Data Management 


- [CustomDataKind](#customdatakind)
- [CustomData](#customdata)
- [API](#api)

The `Custom Data` feature implements a storage for 'any' data, which can be used by other components for data persistence.

Because the schema of the data being persisted varies from components, a `CustomDataKind` must be defined to distinguish the data.

## CustomDataKind

The YAML example below defines a CustomDataKind:

```yaml
name: kind1
kind: CustomDataKind
idField: name
jsonSchema:
  type: object
  properties:
    name:
      type: string
  required:
    - name
```

The `name` field is required, it is the kind name of custom data items of this kind.
The `idField` is optional, and its default value is `name`, the value of this field of a data item is used as its identifier.
The `jsonSchema` is optional, if provided, data items of this kind will be validated against this [JSON Schema](http://json-schema.org/). Use `egctl` to create `CustomDataKind`: `egctl create -f kind1.yaml` or `egctl apply -f kind1.yaml`.

## CustomData

CustomData is a map, the keys of this map must be strings while the values can be any valid JSON values, but the keys of a nested map must be strings too.

A CustomData item must contain the `idField` defined by its corresponding CustomDataKind, for example, the data items of the kind defined in the above example must contain the `name` field as their identifiers.

Below is an example of a CustomData:

```yaml
name: data1
field1: 12
field2: abc
field3: [1, 2, 3, 4]
```

## API

* **Create a CustomDataKind**
        * **URL**: http://{ip}:{port}/apis/v2/customdatakinds
        * **Method**: POST
        * **Body**: CustomDataKind definition is YAML.

* **Update a CustomDataKind**
        * **URL**: http://{ip}:{port}/apis/v2/customdatakinds
        * **Method**: PUT
        * **Body**: CustomDataKind definition is YAML.

* **Query the definition of a CustomDataKind**
        * **URL**: http://{ip}:{port}/apis/v2/customdatakinds/{kind name}
        * **Method**: GET

* **List the definition of all CustomDataKind**
        * **URL**: http://{ip}:{port}/apis/v2/customdatakinds
        * **Method**: GET

* **Delete a CustomDataKind**
        * **URL**: http://{ip}:{port}/apis/v2/customdatakinds/{kind name}
        * **Method**: DELETE

* **Create a CustomData**
        * **URL**: http://{ip}:{port}/apis/v2/customdata/{kind name}
        * **Method**: POST
        * **Body**: CustomData definition is YAML.

* **Update a CustomData**
        * **URL**: http://{ip}:{port}/apis/v2/customdata/{kind name}
        * **Method**: PUT
        * **Body**: CustomData definition is YAML.

* **Query the definition of a CustomData**
        * **URL**: http://{ip}:{port}/apis/v2/customdata/{kind name}/{data id}
        * **Method**: GET

* **List the definition of all CustomData of a kind**
        * **URL**: http://{ip}:{port}/apis/v2/customdata/{kind name}
        * **Method**: GET

* **Delete a CustomData**
        * **URL**: http://{ip}:{port}/apis/v2/customdata/{kind name}/{data id}
        * **Method**: DELETE

* **Delete all CustomData of a kind**
        * **URL**: http://{ip}:{port}/apis/v2/customdata/{kind name}
        * **Method**: DELETE

* **Bulk update**
        * **URL**: http://{ip}:{port}/apis/v2/customdata/{kind name}/items
        * **Method**: POST
        * **Body**: A change request in YAML, as defined below.

```yaml
name: kind1
kind: CustomData
rebuild: false
delete: [data1, data2]
list:
- name: data3
  field1: 12
- name: data4
  field1: foo
```
When `rebuild` is true (default is false), all existing data items are deleted before processing the data items in `list`. `delete` is an array of data identifiers to be deleted, this array is ignored when `rebuild` is true. `list` is an array of data items to be created or updated. `egctl` support to create or apply a change request, `egctl create -f customdata-change-request.yaml` where `name` is `CustomDataKind` name and `kind` is `CustomData`.

---

### 06.Development For Easegress 6.3.Egbuilder (`docs/06.Development-for-Easegress/6.3.egbuilder.md`)

# egbuilder 
`egbuilder` is a command-line tool that enables you to create, build, and run Easegress with custom plugins.


- [Init a custom plugin project](#init-a-custom-plugin-project)
- [Add more plugins](#add-more-plugins)
- [Build Easegress with custom plugins](#build-easegress-with-custom-plugins)
- [Run Easegress in current directory](#run-easegress-in-current-directory)
- [Environment variables](#environment-variables)

## Init a custom plugin project
The `egbuilder init` command helps initialize a custom plugin project, creating the necessary directories and files for users to get started.

```
egbuilder init --repo github.com/your/repo \
    --filters=MyFilter1,MyFilter2 \
    --controllers=MyController1,MyController2
```

The example above will create following directories and files.
```
.
├── .egbuilderrc
├── controllers
│   ├── mycontroller1
│   │   └── mycontroller1.go
│   └── mycontroller2
│       └── mycontroller2.go
├── filters
│   ├── myfilter1
│   │   └── myfilter1.go
│   └── myfilter2
│       └── myfilter2.go
├── go.mod
├── go.sum
└── registry
    └── registry.go
```
The `.egbuilderrc` file is a configuration file that can be used by the `egbuilder add` and `egbuilder run` commands. The `registry/registry.go` file contains code generated to register custom filters and controllers with Easegress. The `controllers` and `filters` directories contain the necessary variables, structures, and methods to get started.

## Add more plugins
The `egbuilder add` command allows you to add more custom filters and controllers to an existing custom plugin project.

```
egbuilder add --filters=MyFilter3,MyFilter4 \ 
    --controllers=MyController3,MyController4
```

The above example will add following directories and files.
```
.
├── controllers
    ...
│   ├── mycontroller3
│   │   └── mycontroller3.go
│   └── mycontroller4
│       └── mycontroller4.go
├── filters
    ...
│   ├── myfilter3
│   │   └── myfilter3.go
│   └── myfilter4
│       └── myfilter4.go
...
```
The `.egbuilderrc` and `registry/registry.go` files will be updated based on changes.

## Build Easegress with custom plugins
The `egbuilder build` command is used to compile Easegress with custom plugins.

```
egbuilder build -f build.yaml
```
where `build.yaml` contains:
```yaml
# egVersion: the version of Easegress used for building. Supports versions v2.5.2 and later.
# An empty egVersion value means using the latest version of Easegress.
egVersion: v2.5.2

# plugins: custom plugins.
# It is recommended to use plugins created with "egbuilder init".
# Generally, any plugin containing "registry/registry.go" can utilize the "egbuilder build" command.
# You can initialize a project to see for yourself.
plugins:
- module: github.com/your/repo
  version: ""
  replacement: "."
- module: github.com/other/repo

# output: path of output file.
output: "./easegress-server"

# raceDetector: "-race" flag for go build
raceDetector: false

# skipBuild: if true, causes egbuilder to not compile the program, it is used in conjunction with build tools such as GoReleaser. Implies skipCleanUp to be true.
skipBuild: false

# skipCleanup: if true, not clean up the temp directory after exiting.
skipCleanup: false

# buildFlags: flags for "go build" command
buildFlags: []

# modFlags: flags for "go mod" command
modFlags: []

# compile: GOOS, GOARCH, GOARM env variable for "go build"
compile:
  os: ""
  arch: ""
  arm: ""
  cgo: false
```

## Run Easegress in current directory
The `egbuilder run` command is used to run Easegress with custom plugins in current working directory.

```bash
egbuilder run              # run with default
egbuilder run -f run.yaml  # run with config
```
where `run.yaml` contains:

```yaml
egVersion: v2.5.2

# egServerArgs: args for easegress-server
# egServerArgs: ["--config-file", "easegress-server.yaml"] means
# ./easegress-server --config-file easegress-server.yaml
egServerArgs: []

raceDetector: false
skipBuild: false
skipCleanup: false
buildFlags: []
modFlags: []
compile:
  os: ""
  arch: ""
  arm: ""
  cgo: false
```

So, `egbuilder run` can be seen as executing `egbuilder build` first, followed by running `./easegress-server`.

## Environment variables
- `EGBUILDER_GO` sets the go command to use when more then one version of go is installed.

---

### 05.Administration 5.1.Config And Cluster Deployment (`docs/05.Administration/5.1.Config-and-Cluster-Deployment.md`)

# Configuration & Cluster Deployment 

- [Background](#background)
- [Prerequisite](#prerequisite)
- [Deploy an Easegress Cluster Step by Step](#deploy-an-easegress-cluster-step-by-step)
  - [Add New Member](#add-new-member)
- [YAML Configuration](#yaml-configuration)
- [Configuration and Environment Variables](#configuration-and-environment-variables)
- [Configuration tips (optional)](#configuration-tips-optional)
- [References](#references)

## Background

When to deploy Easegress as a cluster?
- Your traffic is larger than one machine can handle
- You want to High-availability and minimize the service downtime
- You want to minimize latency on service peaks

It is easy to start multiple Easegress instances to form an Easegress cluster. This tutorial provides instructions on how to create a stand-alone Easegress cluster by starting multiple Easegress instances.

## Prerequisite

The following prerequisites are required for a successful deployment of the Easegress cluster.
- latest `easegress-server` and `egctl` binaries (run `make` in the root of the repository)
- successful creation of an Easegress pipeline (like the Hello World example in the README.md of the repository or any other chapter in [tutorial](../02.Tutorials/README.md) and [cookbook](../03.Advanced-Cookbook/README.md).
- few machines that are in the same network or otherwise accessible or Docker or other container technology. If you only have one machine, then you could use localhost as the host and modify the ports in the example.

## Deploy an Easegress Cluster Step by Step
The goal of this tutorial is to have following infrastructure running Easegress:

<p align="center">
  <img src="../imgs/easegress-cluster-nodes.png">
</p>

- 4 machines connected
- each running Easegress instance
- 3 of Easegress instances have cluster role *primary* and one *secondary*

The difference between *primary* and *secondary* cluster roles is that *primary* persists the cluster state to disk, while *secondary* does not. The number of *secondary* Easegress instances can scale up and down, but the number of *primary* instances should be fixed.

Let's start by creating three Easegress instances with *primary* role. Add nodes private IPs to following environment variables:

```bash
export HOST1=<host1-IP>
export HOST2=<host2-IP>
export HOST3=<host3-IP>
export CLUSTER=machine-1=http://$HOST1:2380,machine-2=http://$HOST2:2380,machine-3=http://$HOST3:2380
```
`CLUSTER` environment variable now contains IP addresses of each member in the cluster. It will be the same for all members.

Set the environment variables to each machine. Start the first instance at the first machine
```bash
easegress-server \
  --cluster-name "multi-node-cluster" \
  --cluster-role "primary" \
  --name "machine-1" \
  --api-addr $HOST1:2381 \
  --initial-advertise-peer-urls http://$HOST1:2380 \
  --listen-peer-urls http://$HOST1:2380 \
  --listen-client-urls http://$HOST1:2379 \
  --advertise-client-urls http://$HOST1:2379 \
  --initial-cluster $CLUSTER
```

Here we define the basic information, like the name of the instance and the name of the cluster. Arguments `initial-advertise-peer-urls`,`listen-peer-urls`, `listen-client-urls` and `advertise-client-urls` are for communication with other peers (other primary cluster members). You can read more about them at the end of this tutorial, but for now, it's enough to notice that hostname for *machine-1* is `$HOST1`, which is the IP address of this machine.

Then start the second instance at machine 2

```bash
easegress-server \
  --cluster-name "multi-node-cluster" \
  --cluster-role "primary" \
  --name "machine-2" \
  --initial-advertise-peer-urls http://$HOST2:2380 \
  --listen-peer-urls http://$HOST2:2380 \
  --listen-client-urls http://$HOST2:2379 \
  --advertise-client-urls http://$HOST2:2379 \
  --initial-cluster $CLUSTER
```

and the last machine 3.

```bash
easegress-server \
  --cluster-name "multi-node-cluster" \
  --cluster-role "primary" \
  --name "machine-3" \
  --initial-advertise-peer-urls http://$HOST3:2380 \
  --listen-peer-urls http://$HOST3:2380 \
  --listen-client-urls http://$HOST3:2379 \
  --advertise-client-urls http://$HOST3:2379 \
  --initial-cluster $CLUSTER
```

Now you can list cluster members

```bash
egctl --server $HOST1:2381 get member 
```
should print something like
```bash
NAME              ROLE            AGE     STATE     API-ADDR     HEARTBEAT
machine-1         primary         10m     Leader    $HOST1:2381  3s ago
machine-2         primary         10m     Follower  $HOST2:2381  4s ago
machine-3         primary         10m     Follower  $HOST3:2381  2s ago
```

###  Add New Member

Let's add one more node with a *secondary* cluster role this time.

```bash
# on machine 4
easegress-server \
  --cluster-name "multi-node-cluster" \
  --cluster-role "secondary" \
  --name "machine-4" \
  --primary-listen-peer-urls http://$HOST1:2380 \
  --state-flag "existing"
```
Here `primary-listen-peer-urls` tell, where to find a *primary* cluster members and `state-flag` with value "existing" means that this cluster was already created.

We can now see also the 4th instance:
```bash
egctl --server $HOST1:2381 get member 
```
prints to output:
```bash
NAME              ROLE            AGE     STATE     API-ADDR     HEARTBEAT
machine-1         primary         10m     Leader    $HOST1:2381  3s ago
machine-2         primary         10m     Follower  $HOST2:2381  4s ago
machine-3         primary         10m     Follower  $HOST3:2381  2s ago
machine-4         secondary       2m      ""        $HOST1:2381  3s ago
```
Congratulations, you now have your Easegress instances running! You can now start applying resources to Easegress, like [httpserver](../02.Tutorials/2.2.HTTP-Proxy-Usage.md) or [pipeline](../02.Tutorials/2.3.Pipeline-Explained.md) for example.

You can also keep reading this tutorial to know more about YAML configuration of Easegress cluster instances or configuration tips.

## YAML Configuration

The examples above use the *easegress-server's* command-line flags, but often it is more convenient to define server parameters in a YAML configuration file. For example, store the following YAML to each host machine and change the host addresses accordingly.

```yaml
# create one yaml file for each host
name: machine-1 # machine-2, machine-3
cluster-name: cluster-test
cluster-role: primary
api-addr: localhost:2381
data-dir: ./data
wal-dir: ""
cpu-profile-file:
memory-profile-file:
log-dir: ./log
debug: false
cluster:
  listen-peer-urls: # change CURRENT-HOST to current host
   - http://<CURRENT-HOST>:2380
  listen-client-urls:
   - http://<CURRENT-HOST>:2379
  advertise-client-urls:
   - http://<CURRENT-HOST>:2379
  initial-advertise-peer-urls:
   - http://<CURRENT-HOST>:2380
  initial-cluster: # initial-cluster is same for every host
   - machine-1: http://<HOST-1>:2380
   - machine-2: http://<HOST-2>:2380
   - machine-3: http://<HOST-3>:2380
```
Then apply these values on each machine, using `config-file` command line argument:
`easegress-server --config-file config.yaml`.

The configuration file for adding a new secondary node looks like following:

```yaml
name: machine-4
cluster-name: cluster-test
cluster-role: secondary
data-dir: ./data
wal-dir: ""
cpu-profile-file:
memory-profile-file:
log-dir: ./log
debug: false
cluster:
  primary-listen-peer-urls:
   - http://$HOST1:2380
```

## Configuration and Environment Variables

In addition to deploying the easegress-server using command-line flags or a YAML file, you can also utilize environment variables. Below are all the available environment variables along with their corresponding flags.

```
/* Detailed source-code truncated for AI context efficiency. */
```

## Configuration tips (optional)

*What is a good size for the cluster?*

It is a good practice to choose an odd number (1,3,5,7,9) of *primary* nodes, to tolerate failures of *primary* nodes. This way the cluster can stay in a healthy state, even if the network partitions. With an even number of *primary* nodes, the cluster can be divided into two groups of equal size due to network partition. Then neither of the sub-clusters have the majority required for consensus. However, with the odd number of *primary* nodes, the cluster cannot be divided into two groups of equal size and this problem cannot occur.

For the *secondary* nodes, there are no constraints for the number of nodes. Secondary nodes do not participate consensus vote of the cluster, so their failure does not affect the cluster health. Adding more (*secondary*) nodes does still increase the communication between nodes.

 *Can a number of primary members scale up?*

Please note that it is not recommended to add additional node with `primary` cluster role, but `primary` nodes should be started at cluster start up. When scaling up the cluster, it is recommended to add and remove `secondary` cluster members.

*What are `advertise-peer-urls` and `listen-client-urls`?*

`advertise-client-urls`, `listen-client-urls`, `listen-peer-urls` and `initial-advertise-peer-urls` are arguments necessary for *primary* cluster members to communicate with other peers (members).

| argument   |  description  |
|-----|-----|
| advertise-client-urls | client URLs member advertises to the rest of the cluster |
| listen-client-urls | URLs that member listens for client traffic |
| listen-peer-urls | URLs that member listens for peer (other *primary* members) traffic  |
| initial-advertise-peer-urls | peer (other *primary* members) URLs member advertises to the rest of the cluster |

These arguments are used for [etcd](https://etcd.io) server and client configuration. You can read more about them in etcd documentation.

*Why *primary* member needs more arguments than *secondary* member ?*

Here's a drawing that illustrates the difference.

<p align="center">
  <img src="../imgs/easegress-cluster-connections.png">
</p>

*Primary* members need to synchronize with peers (other *primary* members). Meanwhile *secondary* can read and update the state through one *primary* member.

*How are etcd server and Easegress primary member related?*

Easegress uses [etcd](https://etcd.io) distributed key-value store to synchronize the cluster state. The primary and secondary cluster roles have the following relation with `etcd`:

| Easegress cluster role   | primary   | secondary   |
|-----|-----|-----|
| etcd term | server | client |

*Primary* member uses etcd server for cluster communication, while *secondary* member uses etcd client for this.


## References

1. https://en.wikipedia.org/wiki/High-availability_cluster
2. https://en.wikipedia.org/wiki/Raft_(algorithm)
3. https://etcd.io/docs/v3.5/faq/

---

### 05.Administration 5.2.Kernel Tuning (`docs/05.Administration/5.2.Kernel-Tuning.md`)

# Kernel Parameters for Lower Latency System

To make Easegress good performance, some of the kernel parameters might need to be optimized. Here is a possible configuration for `/etc/sysctl.conf` and `/etc/security/limits.conf`.

## /etc/security/limits.conf

The values need to be aligned with actual memory.

```conf
*   soft    nproc         2067554
*   hard    nproc         2067554
``` 

## /etc/sysctl.conf

The values need to be aligned with actual memory, CPU, and network.

1. Increase range of ephemeral ports that can be used
    ```conf
    net.ipv4.ip_local_port_range = 1024 65535
    ```

2. Increase the max number of open files
    ```conf
    fs.file-max = 150000
    ```

3. Enable TCP window scaling (enabled by default). Refer to [Wikipedia: TCP_window_scale_option](https://en.wikipedia.org/wiki/TCP_window_scale_option)
    ```conf
    net.ipv4.tcp_window_scaling = 1
    ```

4. Turn off SYN-flood protections
    ```conf
    net.ipv4.tcp_syncookies = 0
    ```

5. Increase the number of packets that can be queued in the network card before being handed to the CPU
    ```conf
    net.core.netdev_max_backlog = 3240000
    ```

6. Max number of "backlogged sockets" (connection requests that can be queued for any given listening socket)
    ```conf
    net.core.somaxconn = 65535
    ```

7. TCP memory tuning. Refer to [Linux Administration](http://www.linux-admins.net/2010/09/linux-tcp-tuning.html)

    ```conf
    # Increase the default socket buffer read size (rmem_default) and write size (wmem_default)
    # *** Maybe recommended only for high-RAM servers? ***
    net.core.rmem_default=16777216
    net.core.wmem_default=16777216
    
    # Increase the max socket buffer size (optmem_max), max socket buffer read size (rmem_max), max     socket buffer write size (wmem_max)
    # 16MB per socket - which sounds like a lot, but will virtually never consume that much
    # rmem_max over-rides tcp_rmem param, wmem_max over-rides tcp_wmem param and optmem_max over-rides     tcp_mem param
    net.core.optmem_max=16777216
    net.core.rmem_max=16777216
    net.core.wmem_max=16777216
    
    # Configure the Min, Pressure, Max values (units are in page size)
    # Useful mostly for very high-traffic websites that have a lot of RAM
    # Consider that we already set the *_max values to 16777216
    # So you may eventually comment on these three lines
    
    net.ipv4.tcp_mem=16777216 16777216 16777216
    net.ipv4.tcp_wmem=4096 87380 16777216
    net.ipv4.tcp_rmem=4096 87380 16777216
    ```

8. Number of packets to keep in the backlog before the kernel starts dropping them
    ```conf
    net.ipv4.tcp_max_syn_backlog = 3240000
    ```

9. Only retry creating TCP connections 3 times. Minimize the time it takes for a connection attempt to fail
    ```conf
    net.ipv4.tcp_syn_retries=3
    net.ipv4.tcp_synack_retries=3
    net.ipv4.tcp_orphan_retries=3
    ```

10. How many retries TCP makes on data segments (default 15). Some guides suggest reducing this value
    ```conf
    net.ipv4.tcp_retries2 = 8
    ```

11. Increase max number of sockets allowed in TIME_WAIT
    ```conf
    net.ipv4.tcp_max_tw_buckets = 1440000
    ```

12. Keepalive Optimizations. By default, the keepalive routines wait for two hours (7200 secs) before sending the first keepalive probe, and then resend it every 75 seconds. If no ACK response is received for 9 consecutive times, the connection is marked as broken.

    ```conf
    # We would decrease the default values for tcp_keepalive_* params as follow:
    net.ipv4.tcp_keepalive_time = 600  # default 7200
    net.ipv4.tcp_keepalive_intvl = 10  # default 75
    net.ipv4.tcp_keepalive_probes = 9  # default 9
    ```

13. The TCP FIN timeout specifies the amount of time a port must be inactive before it can be reused for another connection.  The default is often 60 seconds, but can normally be safely reduced to 30 or even 15 seconds
 https://www.linode.com/docs/web-servers/nginx/configure-nginx-for-optimized-performance

    ```conf
    net.ipv4.tcp_fin_timeout = 7
    ```

14. Refer to this [Github post](https://github.com/ton31337/tools/wiki/tcp_slow_start_after_idle---tcp_no_metrics_save-performance)

    ```conf
    # Avoid falling back to slow start after a connection goes idle.
    net.ipv4.tcp_slow_start_after_idle = 0

    # Disable caching of TCP congestion state
    net.ipv4.tcp_no_metrics_save = 1
    ```

15. If listening service is too slow to accept new connections, reset them. Default state is FALSE. It means that if overflow occurred due to a burst, connection will recover. Enable this option only if you are really sure that listening daemon cannot be tuned to accept connections faster. Enabling this option can harm clients of your server. Refer to [Github Post](https://github.com/ton31337/tools/wiki/Is-net.ipv4.tcp_abort_on_overflow-good-or-not%3F)

    ```conf
    net.ipv4.tcp_abort_on_overflow=0
    ```

---

### 04.Cloud Native 4.1.Kubernetes Ingress Controller (`docs/04.Cloud-Native/4.1.Kubernetes-Ingress-Controller.md`)

# Kubernetes Ingress Controller 


- [Why Use an Ingress Controller](#why-use-an-ingress-controller)
- [Cookbook](#cookbook)
  - [Basic: Handle Ingresses from All K8s namespaces](#basic-handle-ingresses-from-all-k8s-namespaces)
  - [Handle Ingresses within Specified K8s Namespaces](#handle-ingresses-within-specified-k8s-namespaces)
  - [Use a Customized Ingress Class](#use-a-customized-ingress-class)
  - [Deploy Outside of a K8s Cluster](#deploy-outside-of-a-k8s-cluster)
  - [Additional Annotations](#additional-annotations)

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.

This document list example configurations for typical scenarios, more details could be found at [the guide of ingress controller](../07.Reference/7.03.Ingress-Controller.md).

## Why Use an Ingress Controller

- **Cuts Down Infrastructure Costs**: Without an ingress controller, when we wanted to expose 20 services to the public internet, we have to pay for 20 cloud load balancers; but with an ingress controller, we only need to pay for one cloud load balancer.
- **Scalable Layer 7 Load Balancer**
- **Manage Load Balancer Configuration in a Distributed Fashion**

## Cookbook

### Basic: Handle Ingresses from All K8s namespaces

Create an ingress controller, it handles ingresses from all K8s namespaces when `namespaces` is an empty array.

```bash
echo '
kind: IngressController
name: ingress-controller-example
namespaces: []                             # Keep the value an empty array
httpServer:
  port: 8080
  https: false
  keepAlive: true
  keepAliveTimeout: 60s
  maxConnections: 10240
' | egctl create -f -
```

Create two versions of `hello` service in Kubernetes:

```bash
echo '
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-deployment
spec:
  selector:
    matchLabels:
      app: products
      department: sales
  replicas: 2
  template:
    metadata:
      labels:
        app: products
        department: sales
    spec:
      containers:
      - name: hello-v1
        image: "us-docker.pkg.dev/google-samples/containers/gke/hello-app:1.0"
        env:
        - name: "PORT"
          value: "50001"
      - name: hello-v2
        image: "us-docker.pkg.dev/google-samples/containers/gke/hello-app:2.0"
        env:
        - name: "PORT"
          value: "50002"

---
apiVersion: v1
kind: Service
metadata:
  name: hello-service
spec:
  type: NodePort
  selector:
    app: products
    department: sales
  ports:
  - name: port-v1
    protocol: TCP
    port: 60001
    targetPort: 50001
  - name: port-v2
    protocol: TCP
    port: 60002
    targetPort: 50002
' | kubectl apply -f -
```

Create a Kubernetes ingress for the two services, note the `ingressClassName` is `easegress`:

```bash
echo '
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-example
spec:
  ingressClassName: easegress
  rules:
  - host: "www.example.com"
    http:
      paths:
      - pathType: Prefix
        path: /
        backend:
          service:
            name: hello-service
            port:
              number: 60001
  - host: "*.megaease.com"
    http:
      paths:
      - pathType: Prefix
        path: /
        backend:
          service:
            name: hello-service
            port:
              number: 60002
' | kubectl apply -f -
```

After a while, we can leverage the below command to access both versions of the `hello` application:

```bash
$ curl http://{NODE_IP}:{NODE_PORT}/ -HHost:www.megaease.com
Hello, world!
Version: 2.0.0
Hostname: hello-deployment-6cbf765985-r6242

$ curl http://{NODE_IP}:{NODE_PORT}/ -HHost:www.example.com
Hello, world!
Version: 1.0.0
Hostname: hello-deployment-6cbf765985-r6242
```

* NOTE:

You can get the `NODE_IP` and `NODE_PORT` by running the following command:

```text
export NODE_PORT=$(kubectl get --namespace easegress -o jsonpath="{.spec.ports[0].nodePort}" services easegress)
export NODE_IP=$(kubectl get nodes --namespace easegress -o jsonpath="{.items[0].status.addresses[0].address}")
echo http://$NODE_IP:$NODE_PORT
```

### Handle Ingresses within Specified K8s Namespaces

When one or more K8s namespaces are listed in `namespaces`, the ingress controller only handles ingresses within these namespaces.

```yaml
kind: IngressController
name: ingress-controller-example
namespaces: ['sales', 'customer']          # List namespaces here
httpServer:
  port: 8080
  https: false
  keepAlive: true
  keepAliveTimeout: 60s
  maxConnections: 10240
```

### Use a Customized Ingress Class

By default, the ingress controller handles ingresses with `IngressClassName` set to `easegress`, this can be changed by specifying the `ingressClass` in configuration.

```yaml
kind: IngressController
name: ingress-controller-example
namespaces: []
ingressClass: myingress                    # specify the target ingress class
httpServer:
  port: 8080
  https: false
  keepAlive: true
  keepAliveTimeout: 60s
  maxConnections: 10240
```

### Deploy Outside of a K8s Cluster

When deployed outside of a K8s Cluster, we must specify `kubeConfig` or `masterURL` or both of them.

```yaml
kind: IngressController
name: ingress-controller-example
kubeConfig: /home/megaease/.kube/config    # Path of the K8s configuration file
masterURL: http://localhost:8080/api/      # URL of the K8s API server
namespaces: []
httpServer:
  port: 8080
  https: false
  keepAlive: true
  keepAliveTimeout: 60s
  maxConnections: 10240
```

### Additional Annotations

When defining your `Ingress` configurations, you can use the following `easegress` specific annotations for added customization:

- **Load Balancing**: `easegress.ingress.kubernetes.io/proxy-load-balance`
- **Header Hash Key for Load Balancing**: `easegress.ingress.kubernetes.io/proxy-header-hash-key`
- **Forwarding Key**: `easegress.ingress.kubernetes.io/proxy-forward-key`
- **Server Response Maximum Size**: `easegress.ingress.kubernetes.io/proxy-server-max-size`
- **Proxy Timeout**: `easegress.ingress.kubernetes.io/proxy-timeout`

Please refer to the [Proxy Server Pool Spec](../07.Reference/7.02.Filters.md#proxyserverpoolspec) and [Load Balance Spec](../07.Reference/7.02.Filters.md#proxyloadbalancespec) to get the details of the corresponding annotations.

These annotations provide additional configurations to optimize the behavior and performance of your ingress controllers in different scenario.

```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-example
  annotations:
    easegress.ingress.kubernetes.io/proxy-load-balance: "ipHash"
    easegress.ingress.kubernetes.io/proxy-header-hash-key: "X-User-Id"
    easegress.ingress.kubernetes.io/proxy-forward-key: ""
    easegress.ingress.kubernetes.io/proxy-server-max-size: "12400"
    easegress.ingress.kubernetes.io/proxy-timeout: "10s"
spec:
  ingressClassName: easegress
  rules:
  - host: "www.example.com"
    http:
      paths:
      - pathType: Prefix
        path: /
        backend:
          service:
            name: hello-service
            port:
              number: 60001
  - host: "*.megaease.com"
    http:
      paths:
      - pathType: Prefix
        path: /
        backend:
          service:
            name: hello-service
            port:
              number: 60002
```

---

### 04.Cloud Native 4.2.Gateway API (`docs/04.Cloud-Native/4.2.Gateway-API.md`)

# Kubernetes Gateway API 

- [Prerequisites](#prerequisites)
- [Getting Started](#getting-started)
  - [Role Based Access Control configuration](#role-based-access-control-configuration)
  - [Config Map](#config-map)
  - [Deploy Easegress GatewayController](#deploy-easegress-gatewaycontroller)
  - [Deploy Backend Services](#deploy-backend-services)
  - [Deploy Gateway API Objects](#deploy-gateway-api-objects)
  - [Expose The Listener Port](#expose-the-listener-port)
  - [Verification](#verification)
  - [Extensions](#extensions)


The `GatewayController` is an implementation of
[Kubernetes Gateway API](https://gateway-api.sigs.k8s.io/), it watches
Kubernetes Gateway, HTTPRoute, Service, and Secrets then translates them to
Easegress HTTP server and pipelines.

**Note** the currenct `GatewayController` is an experimental implementation, NOT all the features of the `Kubernetes Gateway API` features are supported.

## Prerequisites

1. K8s cluster : **v1.23+**
2. Gateway API: following https://gateway-api.sigs.k8s.io/guides/#installing-gateway-api
   to install.

## Getting Started

### Role Based Access Control configuration

If your cluster is configured with RBAC, first you will need to authorize
Easegress GatewayController for using the Kubernetes API. Below is an example
configuration:

```yaml
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: easegress-gateway-controller
rules:
- apiGroups: [""] # "" indicates the core API group
  resources: ["services", "secrets", "endpoints", "namespaces"]
  verbs: ["get", "watch", "list"]
- apiGroups: ["gateway.networking.k8s.io"]
  resources: ["gatewayclasses", "httproutes", "gateways"]
  verbs: ["get", "watch", "list"]
- apiGroups: ["gateway.networking.k8s.io"]
  resources: ["gatewayclasses/status", "gateways/status"]
  verbs: ["update"]
# used for extensions
- apiGroups: ["easegress.megaease.com"]
  resources: ["filterspecs"]
  verbs: ["get", "watch", "list"]

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: easegress-gateway-controller
  namespace: default

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: easegress-gateway-controller
subjects:
- kind: ServiceAccount
  name: easegress-gateway-controller
  namespace: default
roleRef:
  kind: ClusterRole
  name: easegress-gateway-controller
  apiGroup: rbac.authorization.k8s.io
```

Note the name of the ServiceAccount we just created is `easegress-gateway-controller`,
it will be used later.

### Config Map

Let's use ConfigMap to store Easegress server configuration and Easegress
gateway configuration. This ConfigMap is used later in Deployment.

```yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: easegress-cm
  namespace: default
data:
  easegress-server.yaml: |
    name: gateway-easegress
    cluster-name: easegress-gateway-controller
    cluster-role: primary
    api-addr: 0.0.0.0:2381
    data-dir: /opt/easegress/data
    log-dir: /opt/easegress/log
    debug: false
  controller.yaml: |
    kind: GatewayController
    name: gateway-controller-example
    kubeConfig:
    masterURL:
    namespaces: []
```

The `easegress-server.yaml` creates Easegress instance named *easegress-gateway-controller*
and `controller.yaml` defines GatewayController object for Easegress.

### Deploy Easegress GatewayController

To deploy the GatewayController, we will create a Deployment and a Service as below:

```
/* Detailed source-code truncated for AI context efficiency. */
```

The GatewayController is created via the command line argument `initial-object-config-files`
of `easegress-server`. Note that Easegress logs and data are stored to *emptyDir* called
`gateway-data-volume` inside the pod as GatewayController is stateless so we can
restart new pods without preserving previous state.

### Deploy Backend Services

Apply below YAML configuration to Kubernetes:

```yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-deployment
spec:
  selector:
    matchLabels:
      app: products
      department: sales
  replicas: 2
  template:
    metadata:
      labels:
        app: products
        department: sales
    spec:
      containers:
      - name: hello-v1
        image: "us-docker.pkg.dev/google-samples/containers/gke/hello-app:1.0"
        env:
        - name: "PORT"
          value: "50001"
      - name: hello-v2
        image: "us-docker.pkg.dev/google-samples/containers/gke/hello-app:2.0"
        env:
        - name: "PORT"
          value: "50002"

---
apiVersion: v1
kind: Service
metadata:
  name: hello-service
spec:
  type: NodePort
  selector:
    app: products
    department: sales
  ports:
  - name: port-v1
    protocol: TCP
    port: 60001
    targetPort: 50001
  - name: port-v2
    protocol: TCP
    port: 60002
    targetPort: 50002
```

### Deploy Gateway API Objects

Please refer [Kubernetes Gateway API](https://gateway-api.sigs.k8s.io/) for the detailed information
of Gateway Class, Gateway and HTTPRoute configuration. Up to now, the Easegress
GatewayController only support HTTP & HTTPS listeners.

```yaml
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
  name: example-gateway-class
spec:
  controllerName: "megaease.com/gateway-controller"

---

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: example-gateway
spec:
  gatewayClassName: example-gateway-class
  listeners:
  - protocol: HTTP
    port: 8080
    name: example-listener

---

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: example-route
spec:
  parentRefs:
  - kind: Gateway
    name: example-gateway
    sectionName: example-listener
  rules:
  - matches:
    - path:
        value: /1
    backendRefs:
    - name: hello-service
      port: 60001

---

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: example-route-2
spec:
  parentRefs:
  - kind: Gateway
    name: example-gateway
    sectionName: example-listener
  hostnames:
    - megaease.com
  rules:
  - matches:
    - path:
        value: /2
    backendRefs:
    - name: hello-service
      port: 60002
```

### Expose The Listener Port

Create service to forward the incoming traffic to Listener in Easegress.

```yaml
apiVersion: v1
kind: Service
metadata:
  name: easegress-public
  namespace: default
spec:
  ports:
  - name: web
    protocol: TCP
    port: 8080
    nodePort: 30080
  selector:
    app: easegress-gateway
  type: NodePort
  ```

The port `web` is to receive external HTTP requests from port 30080 and forward
them to the HTTP server created according to the Listener configuration of
the Gateway.

### Verification

Once all configurations are applied, we can leverage the command below to access
both versions of the `hello` application:

```bash
$ curl http://127.0.0.1:30080/1
Hello, world!
Version: 2.0.0
Hostname: hello-deployment-7855bc9747-n4qsx

$ curl http://127.0.0.1:30080/2 -i
HTTP/1.1 404 Not Found
Date: Wed, 19 Jul 2023 06:59:07 GMT
Content-Length: 0
Connection: close

$ curl http://127.0.0.1:30080/2 -H Host:megaease.com
Hello, world!
Version: 2.0.0
Hostname: hello-deployment-7855bc9747-n4qsx
```

### Extensions 

To leverage additional Easegress features (such as more filters and resilience policies) with the Kubernetes Gateway API, you can use `extensionRef`s in `HTTPRoute`.


First, you need to create custom resources:

```yaml
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: filterspecs.easegress.megaease.com
spec:
  group: easegress.megaease.com
  versions:
    - name: v1
      served: true
      storage: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                name:
                  type: string
                kind:
                  type: string
                spec:
                  type: string
  scope: Namespaced
  names:
    plural: filterspecs
    singular: filterspec
    kind: FilterSpec
```

Next, define some custom resources:

```yaml
apiVersion: easegress.megaease.com/v1
kind: FilterSpec
metadata:
  name: rate-limiter
spec:
  name: rate-limiter
  kind: RateLimiter
  spec: |
    policies:
    - name: policy
      limitRefreshPeriod: 1000ms
      limitForPeriod: 1
    defaultPolicyRef: policy
    urls:
    - url:
        prefix: /
      policyRef: policy

---

apiVersion: easegress.megaease.com/v1
kind: FilterSpec
metadata:
  name: circuit-breaker
spec:
  name: circuit-breaker
  kind: CircuitBreaker
  spec: |
    slidingWindowType: COUNT_BASED
    failureRateThreshold: 50
    slidingWindowSize: 100
    slowCallRateThreshold: 60
    slowCallDurationThreshold: 30s
    minimumNumberOfCalls: 10
    waitDurationInOpenState: 2m
    maxWaitDurationInHalfOpenState: 1m
    permittedNumberOfCallsInHalfOpenState: 10
```

Here, we define a `RateLimiter` filter and a `CircuitBreaker` resilience policy.

These can be referenced in an `HTTPRoute`:

```yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: example-route-3
spec:
  parentRefs:
  - kind: Gateway
    name: example-gateway
    sectionName: example-listener
  hostnames:
    - megaease.com
  rules:
  - matches:
    - path:
        value: /3
    filters: 
    - type: ExtensionRef
      extensionRef:
        group: "easegress.megaease.com"
        kind: "FilterSpec"
        name: "rate-limiter"
    - type: ExtensionRef
      extensionRef:
        group: "easegress.megaease.com"
        kind: "FilterSpec"
        name: "circuit-breaker"
    backendRefs:
    - name: hello-service
      port: 60002
```

With this setup, the `rate-limiter` and `circuit-breaker` from `FilterSpec` in the `easegress.megaease.com` group are utilized in the `HTTPRoute`. In fact, all Easegress filters and resilience policies can be extended in this manner.

The execution order of these filters is as follows:

- Filters in `extensionRef`, except for `ResponseAdaptor` and `ResponseBuilder`, in the order of their definition.
- Kubernetes built-in filters, such as `RequestHeaderModifier`.
- `ResponseAdaptor` and `ResponseBuilder`, if they exist.

---

### 04.Cloud Native 4.3.EaseMesh (`docs/04.Cloud-Native/4.3.EaseMesh.md`)

# EaseMesh  

`EaseMesh` is a service mesh that is compatible with the Spring Cloud ecosystem. It is based on `Easegress` for the sidecar of service management and `EaseAgent` for the monitor of service observing.

- [Features](#features)
- [Link](#link)


## Features

- **Non-intrusive Design**: Zero code modification for Java Spring Cloud application migration, only small configuration update needed.
- **Java Register/Discovery**: Compatible with popular Java Spring Cloud ecosystem's Service registry/discovery.
  - **Multiple tenants(namespace)** Supporting multiple tenants' service registration, isolate services from different tenants.
  - **Share (global) tenant** Support share tenants, all services have visibility to the service registered in the global tenant.
  - **Compatible**
    - Be compatible with the Eureka registry.
    - Be compatible with the Consul registry.
    - Be compatible with the Nacos registry.
  - **Extensibility** Support registering services with metadata.
- **Resource Management**: Rely on Kubernetes platform for CPU/Memory resources management.
- **Traffic Orchestration**
	- **Rich Routing Rules:** Exact path, path prefix, regular expression of the path, method, headers.
  - **Traffic Splitting** Coloring & Scheduling east-west and north-south traffic to configured services.
  - **LoadBalance** Support Round Robin, Weight Round Robin, Random, Hash by Client IP Address, Hash by HTTP Headers.
- **Resilience**: Including Timeout/CircuitBreaker/Retryer/Limiter, completely follow sophisticated resilience design.
	- **Resilience&Fault Tolerance**
		- **Circuit breaker:** Temporarily blocks possible failures.
		- **Rate limiter:** Limits the rate of incoming requests.
		- **Retryer:** Repeats failed executions.
		- **Time limiter:** Limits the duration of execution.
  - **Chaos engineering**
    - **Fault injection** *Working in progress.*
    - **Delay injection** *Working in progress.*
- **Observability**:
  - **Logs**
    - **Access Logs** Generate HTTP access log for all requests per service.
    - **Application log** Automatically inject the tracing context into log data.
  - **Tracing**
    - **JDBC** Tracing for invocation of the JDBC.
    - **HTTP Request** Tracing for HTTP RPC.
    - **Kafka** Tracing for messages delivered by Kafka.
    - **Redis** Tracing for Redis cache accessing.
    - **RabbitMQ** Tracing for messages delivered by the RabbitMQ.
    - **Sampling**
      - Support probabilistic sampling.
      - Support QPS sampling.
  - **Metrics**
    - **HTTP Request** Reporting throughput latency per URL.
    - **JDBC**  Reporting throughput and latency per SQL.
    - **Kafka** Reporting throughput and latency per consumer, producer, and topic.
    - **Redis** Reporting throughput and latency per method.
    - **RabbitMQ** Reporting throughput and latency per topic.
- **Security**
  - **mTLS** *Working in progress.*
  - **mTLS Enforcement** *Working in progress.*
  - **External CA certificate** *Working in progress.*
  - **Service-to-Service Authorization Rules** *Working in progress.*

> The throughput is represented by m1, m5, m15
> The latency is represented by P99, P98, P95, P90, P80, P75, P50, etc...

## Link

For more details, please check [EaseMesh](https://github.com/megaease/easemesh).

---

### 03.Advanced Cookbook 3.01.MQTT Proxy (`docs/03.Advanced-Cookbook/3.01.MQTT-Proxy.md`)

# 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.
  If Kafka is unavailable when the filter starts, `KafkaMQTT` keeps retrying producer creation and returns `producerUnavailable` until the producer is ready.
  A temporary delivery error appears in the filter's `health` status, which returns to `ready` after Kafka confirms a successful delivery.

## 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

---

### 03.Advanced Cookbook 3.02.Flash Sale (`docs/03.Advanced-Cookbook/3.02.Flash-Sale.md`)

# 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](../03.Advanced-Cookbook/3.07.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<string, string>) {
		super(params)
	}

	// run will be called for every request
	run(): i32 {
		return 0
	}
}

// register a factory method of the FlashSale program
registerProgramFactory((params: Map<string, string>) => {
	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<string, string>) {
		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<string, string>) => {
	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<string, string>) {
		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<string, string>) => {
	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](../02.Tutorials/2.5.Traffic-Verification.md) 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<string, string>) {
		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<string, string>) => {
	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<string, string>) {
		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<string, string>) => {
	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<string, string>) {
		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
{}
```

The above example is only a demo.
A production flash-sale solution also needs crawler and abuse protection, capacity planning, and business-specific controls.
Open a [GitHub issue](https://github.com/easegress-io/easegress/issues/new/choose) if you want to discuss Easegress capabilities for this use case.

## 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.

---

### 03.Advanced Cookbook 3.03.Multiple API Orchestration (`docs/03.Advanced-Cookbook/3.03.Multiple-API-Orchestration.md`)

# Build A Telegram Translation Bot With Easegress 

Easegress is the next-generation traffic-based gateway product. 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:

* 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:



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:

```
/* Detailed source-code truncated for AI context efficiency. */
```

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:



## 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.

---

### 03.Advanced Cookbook 3.04.Canary Release (`docs/03.Advanced-Cookbook/3.04.Canary-Release.md`)

# 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



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



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



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: /<Path>/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"}
```

---

### 03.Advanced Cookbook 3.05.Distributed Tracing (`docs/03.Advanced-Cookbook/3.05.Distributed-Tracing.md`)

# 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.

```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](../07.Reference/7.01.Controllers.md#tracingspec) for details.



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. 



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"

---

### 03.Advanced Cookbook 3.06.Service Registry (`docs/03.Advanced-Cookbook/3.06.Service-Registry.md`)

# 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](../07.Reference/7.01.Controllers.md#consulserviceregistry), [Etcd](../07.Reference/7.01.Controllers.md#etcdserviceregistry), [Zookeeper](../07.Reference/7.01.Controllers.md#zookeeperserviceregistry), [Eureka](../07.Reference/7.01.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
```

---

### 03.Advanced Cookbook 3.07.WasmHost (`docs/03.Advanced-Cookbook/3.07.WasmHost.md`)

# 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 Easegress officil.
* **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<string, string>) {
			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<string, string>) => {
		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<string, string>) => {
	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<string, string>) {
		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<string, string>) => {
	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<string, string>) => {
	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<string, string>) => {
	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<string, string>) => {
	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<string, string>) => {
	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
```

---

### 03.Advanced Cookbook 3.08.ChatGPT Bot (`docs/03.Advanced-Cookbook/3.08.ChatGPT-Bot.md`)

# Build A Telegram ChatGPT Bot With Easegress 


Before reading this article, please read the [Multiple API Orchestration](3.03.Multiple-API-Orchestration.md) article to ensure an understanding of Easegress API orchestration and Telegram bot.

In this article, we will introduce how to build a Telegram ChatGPT bot with Easegress. This bot can generate a response based on the user's input.

- [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 Check and Extract Text](#42-check-and-extract-text)
  - [4.3 Send Request to ChatGPT](#43-send-request-to-chatgpt)
  - [4.4 Send Reply Message to Telegram](#44-send-reply-message-to-telegram)
  - [4.5 Response](#45-response)
- [5. Deploy](#5-deploy)


## 1. Prerequisites

Since the bot needs to receive Telegram message notifications and call ChatGPT APIs, we must prepare the following in advance:

* Install the latest version of Easegress according to [this document](../01.Getting-Started/1.2.Install.md) 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 (EaseChatGPTBot 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.
* Create a [OpenAI](https://openai.com/blog/openai-api/) account and write down its API key.

## 2. How it works

The diagram below shows the workflow of this bot:



Upon receiving a notification of a new message from the Telegram server via webhook, the bot first checks the message type. If it is a text message, then call the [ChatGPT API](https://openai.com/blog/openai-api/) and send the response to the user via Telegram.

## 3. Pipeline

First, let's check the overall flow of the Pipeline:

```yaml
name: chatgpt-pipeline
kind: Pipeline

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

# check request type, only process text message
- filter: checkRequestType
  jumpIf:
    result0: processText
    "": END                              # ignore, end the processing

# process text message
- filter: requestBuilderExtractText
  alias: processText                     # alias of the filter
  namespace: extract                     # namespace the filter belongs to

# send to ChatGPT
- filter: requestBuilderChatGPT          # send request to ChatGPT
  alias: chatGPT
  namespace: gpt
- filter: proxyChatGPT
  namespace: gpt

# send reply message to Telegram
- filter: requestBuilderReply            # constructing a request to send the reply
  namespace: tg
- filter: proxyTelegram                  # send the request to Telegram
  namespace: tg
```

## 4. Filter

### 4.1 Backend Proxies

We need to create two proxies to send requests to the ChatGPT API server and the Telegram API server. The configuration of the two proxies is as follows:

```yaml
- name: proxyChatGPT
  kind: Proxy
  pools:
  - servers:
    - url: https://api.openai.com

- name: proxyTelegram
  kind: Proxy
  pools:
  - servers:
    - url: https://api.telegram.org
```

### 4.2 Check and Extract Text

The processing details are the same as that in [Multiple API Orchestration](../03.Advanced-Cookbook/3.03.Multiple-API-Orchestration.md).

```yaml
filters:
- kind: ResultBuilder
  name: checkRequestType
  template: |
    {{- $msg := or .requests.DEFAULT.JSONBody.message .requests.DEFAULT.JSONBody.channel_post -}}
    {{- if $msg.text}}result0{{end -}}

- kind: RequestBuilder
  name: requestBuilderExtractText
  template: |
    {{- $msg := or .requests.DEFAULT.JSONBody.message .requests.DEFAULT.JSONBody.channel_post -}}
    body: |
      {
         "exclude": true,
         "text": "{{$msg.text | jsonEscape}}"
      }
```

### 4.3 Send Request to ChatGPT

Build a request to send to ChatGPT. See more details in [OpenAI API](https://platform.openai.com/docs/guides/chat). Make sure to replace the `{Your OpenAI API Key}` with your own API key.

```yaml
- kind: RequestBuilder
  name: requestBuilderChatGPT
  template: |
    method: POST
    url: /v1/chat/completions
    headers:
      "Authorization": ["Bearer {Your OpenAI API Key}"]
      "Content-Type": ["application/json"]
    body: |
      {
         "model": "gpt-3.5-turbo",
         "messages": [{
            "role": "user",
            "content": "{{.requests.extract.JSONBody.text | jsonEscape}}"
         }]
      }
```

### 4.4 Send Reply Message to Telegram

Make sure to replace the `{YOUR BOT TOKEN}` with your own bot token.

```yaml
- kind: RequestBuilder
  name: requestBuilderReply
  template: |
    {{$msg := or .requests.DEFAULT.JSONBody.message .requests.DEFAULT.JSONBody.channel_post}}
    {{$resp := index .responses.gpt.JSONBody.choices 0}}
    method: POST
    # Please replace it with the url of your bot
    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": "{{$resp.message.content | jsonEscape}}"
      }
```

### 4.5 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/chatgpt-bot)), we can deploy this Pipeline to Easegress (assuming the file name is `chatgpt-pipeline.yaml`) via the following command.

```bash
$ egctl create -f chatgpt-pipeline.yaml
```

Create a HTTPServer to receive the webhook from Telegram.

```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: /chatgpt
    backend: chatgpt-pipeline' | egctl create -f -
```

Now, we can test the bot in the chat.

---

### 03.Advanced Cookbook 3.09.FaaS (`docs/03.Advanced-Cookbook/3.09.FaaS.md`)

# FaaS 

- [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.04.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. [faas](../07.Reference/7.04.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
```

---

### 03.Advanced Cookbook 3.10.Workflow (`docs/03.Advanced-Cookbook/3.10.Workflow.md`)

# Workflow 

- [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.

```
/* Detailed source-code truncated for AI context efficiency. */
```

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

---

### 03.Advanced Cookbook 3.11.Performance (`docs/03.Advanced-Cookbook/3.11.Performance.md`)

# 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)

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
```

---

### 03.Advanced Cookbook 3.12.Migrate (`docs/03.Advanced-Cookbook/3.12.Migrate.md`)

# Migrate v1.x Filter To v2.0 

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.
Most of the work should be straightforward, but please open a
[GitHub issue](https://github.com/easegress-io/easegress/issues/new/choose)
if you need help.

---

### 03.Advanced Cookbook 3.13.Nginx (`docs/03.Advanced-Cookbook/3.13.Nginx.md`)

# Nginx Configuration Conversion to Easegress YAML

- [Nginx Configuration Conversion to Easegress YAML](#nginx-configuration-conversion-to-easegress-yaml)
  - [Nginx Configurations Supported for Conversion](#nginx-configurations-supported-for-conversion)
    - [listen](#listen)
    - [server\_name](#server_name)
    - [location](#location)
    - [proxy\_pass and upstream](#proxy_pass-and-upstream)
    - [websocket](#websocket)
    - [HTTPS](#https)
    - [proxy\_set\_header](#proxy_set_header)
    - [gzip](#gzip)
  - [Example](#example)

This document serves as a guide for converting Nginx configurations to Easegress YAML format using the `egctl convert nginx` command.

```bash
egctl convert nginx -f <nginx.conf> -o <output.yaml> --prefix <prefix>
```

## Nginx Configurations Supported for Conversion

### listen
The `listen` directive supports `port`, `address:port`, and `ssl` configurations. It corresponds to the `port` and `address` properties of an Easegress `HTTPServer`.

```
listen: 80;
listen: 127.0.0.1:80;
listen: 443 ssl;
```

### server_name
Supports exact hostnames, prefixed hostnames, suffixed hostnames, and regular expressions. Translates to the `hosts` attribute in Easegress `HTTPServer`.

```
server_name www.example.com *.example.com;
server_name www.example.* ~^(?<user>.+)\.example\.net$
```

### location
Handles `location` with prefix paths, exact paths, and regular expression paths (case-insensitive included). The ordering in Easegress follows Nginx's precedence: exact paths first, prefix paths next (sorted by length), and regex paths last. Nested locations are also supported.

This translates to `rules` and `paths` in an Easegress `HTTPServer`.

```
location /apis {
    location /apis/v1 {
        ...
    }
    ...
}

location = /user {
    ...
}

location ~* \.(gif|jpg|jpeg)$ {
    ...
}

location ^~ /admin/ {
    ...
}
```

The resulting path order in Easegress would be:

```
- path: /user
- pathPrefix: /apis/v1
- pathPrefix: /admin/
- pathPrefix: /apis
- pathRegexp: \.(gif|jpg|jpeg)$
```

### proxy_pass and upstream
Currently, only reverse proxy functionalities are supported. For `upstream`, the default load-balancing strategy is `roundRobin`. If a server weight is specified, `weightRandom` is used for load balancing.

Equal to Easegress `Pipeline` with `Proxy` filter.

```
proxy_pass http://user.example.com

proxy_pass http:backend

upstream backend {
    server 127.0.0.1:8080;
    server 127.0.0.2:9090 weight=10;
}
```

### websocket
Settings recognized as `WebSocket` configurations correspond to an Easegress `Pipeline` with a `WebsocketProxy` filter.

```
location /websocket {
    proxy_pass http://wsbackend;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "Upgrade";
}
```

### HTTPS
HTTPS configurations are supported as per Easegress `HTTPServer` settings.

```
listen 443 ssl;

ssl_certificate        you-cert.crt;
ssl_certificate_key    your-cert-key.key;

ssl_certificate        you-cert2.crt;
ssl_certificate_key    your-cert-key2.key;

ssl_client_certificate your-client-cert.crt;
```

### proxy_set_header
Translates to an Easegress `Pipeline` with a `RequestAdaptor` filter.

The following Nginx embedded variables are supported:

- `$host`
- `$hostname`
- `$content_length`
- `$content_type`
- `$remote_addr`
- `$remote_user`
- `$request_body`
- `$request_method`
- `$request_uri`
- `$scheme`

For example:
```
proxy_set_header Host $host
```

### gzip
Corresponds to an Easegress `Pipeline` with a `Proxy` filter, including `compression` settings.

```
gzip on;
gzip_min_length 1000;
```

## Example 

Given following nginx configuration:
```
events {}
http {
    upstream backend {
        server localhost:1234;
        server localhost:2345 weight=10;
    }

    server {
        listen 80;

        server_name www.example.com *.example.com;

        location /apis {
            proxy_set_header X-Path "apis";
            proxy_pass http://localhost:8880;

            location /apis/v1 {
                proxy_set_header X-Path "apis/v1";
                proxy_pass http://localhost:8888;
            }
        }

        location /upstream {
            gzip on;
            gzip_min_length 1000;
            proxy_pass http://backend;
        }

        location /websocket {
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection $connection_upgrade;
            proxy_pass http://localhost:9090;
        }
    }

    server {
        listen 127.0.0.1:443 ssl;
        ssl_client_certificate <your-client-cert>;
        ssl_certificate <your-cert>;
        ssl_certificate_key <your-key>;

        location = /user {
            proxy_pass http://localhost:9999;
        }
    }
}
```

then run 

```bash
egctl convert ngxin -f nginx.conf -o nginx.yaml --prefix convert-nginx
```

will generate following Easegress YAML:

```
/* Detailed source-code truncated for AI context efficiency. */
```

---

### 03.Advanced Cookbook 3.14.WAF (`docs/03.Advanced-Cookbook/3.14.WAF.md`)

# Web Application Firewall 


- [Introduction](#introduction)
- [Configuring Web Application Firewall](#configuring-web-application-firewall)
  - [Custom](#custom)
  - [OWASP Core Rule Set](#owasp-core-rule-set)
  - [IP Blocker](#ip-blocker)
    - [Whitelist Mode](#whitelist-mode)
    - [Blacklist Mode](#blacklist-mode)
    - [Combined Mode (Whitelist \& Blacklist)](#combined-mode-whitelist--blacklist)
  - [GEO IP Blocker](#geo-ip-blocker)
    - [Whitelist Mode (allowedCountries)](#whitelist-mode-allowedcountries)
    - [Blacklist Mode (deniedCountries)](#blacklist-mode-deniedcountries)
  - [Rate Limiter](#rate-limiter)
- [Observability](#observability)


## Introduction

The Web Application Firewall (WAF) protects your apps by inspecting HTTP(S) traffic and blocking malicious requests at the application layer. It supports standard rule sets like OWASP CRS and flexible custom policies.

Beyond basic request filtering, the WAF includes IP/Geo access control, rate limiting, and rich observability for tuning and incident response. This ensures strong protection with low false positives and clear operational insight.

## Configuring Web Application Firewall

Here is a simple example shows how to configure Easegress to protect your backend using a WAF with a minimal subset of the OWASP Core Rule Set (CRS) focused on SQL Injection (SQLi) detection.

```yaml
name: waf-controller
kind: WAFController
ruleGroups:
- name: sqlinjection
  rules:
    customRules: |
      // https://github.com/corazawaf/coraza-coreruleset/blob/main/rules/%40crs-setup.conf.example
      // check coraza core rule set recommend setup file 
    owaspRules:
    - REQUEST-901-INITIALIZATION.conf
    - REQUEST-942-APPLICATION-ATTACK-SQLI.conf
    - REQUEST-949-BLOCKING-EVALUATION.conf

------

name: waf-server
kind: HTTPServer
port: 10080
rules:
- paths: 
  - pathPrefix: /
    backend: waf-pipeline

-----

name: waf-pipeline
kind: Pipeline
filters:
- name: waf-filter
  kind: WAF
  ruleGroup: sqlinjection
- name: proxy
  kind: Proxy
  pools:
  - servers:
    - url: http://127.0.0.1:9095
    - url: http://127.0.0.1:9096
    loadBalance:
      policy: roundRobin
```

It will do following things:

1. Loads a minimal, SQLi-focused subset of the OWASP CRS:
    - 901: Initialization and variable setup
    - 942: SQL Injection detection
    - 949: Final blocking evaluation
2. Routes traffic to port 10080 through the waf-pipeline, where the WAF filter evaluates requests against the sqlinjection rule group before proxying to your backend pool.

### Custom

This example shows how to load the full OWASP Core Rule Set (CRS) and add your own custom Coraza/ModSecurity rules in Easegress. The example below blocks all POST requests at both `phase 1` and `phase 2` for demonstration purposes.

```yaml
name: waf-controller
kind: WAFController
ruleGroups:
- name: sqlinjection
  rules:
    loadOwaspCrs: true
    customRules: |
      SecRule REQUEST_METHOD "POST" "id:1000001,phase:1,block,log,msg:'Block all POST requests (phase 1)',severity:'CRITICAL'"
      SecRule REQUEST_METHOD "POST" "id:1000002,phase:2,block,log,msg:'Block all POST requests (phase 2)',severity:'CRITICAL'"
```

What this does:

- `loadOwaspCrs`: true loads the full OWASP CRS bundle. You can includes them as you needed. For full rule set, please check: [here](https://github.com/corazawaf/coraza-coreruleset/tree/main/rules/%40owasp_crs).
- `customRules` adds two rules that explicitly block all POST requests. They run in different phases to illustrate the request processing lifecycle. For more details about Coraza/Modsecurity rules please check: [here](https://coraza.io/docs/seclang/)

Feel free to add your own rules as needed.

### OWASP Core Rule Set

This example shows how to use the corazawaf/coraza-coreruleset package to protect your applications. This package provides a straightforward way to embed the official OWASP Core Rule Set (CRS) directly into a Go application using the Coraza Web Application Firewall (WAF).

To effectively use the CRS, a proper setup is essential. This involves including foundational configuration files that define the behavior of both the Coraza engine and the rule set itself.

Coraza Configuration [@coraza.conf-recommended](https://github.com/corazawaf/coraza-coreruleset/blob/main/rules/%40coraza.conf-recommended): This file provides the recommended base settings for the Coraza WAF engine.

CRS Setup [@crs-setup.conf.example](https://github.com/corazawaf/coraza-coreruleset/blob/main/rules/%40crs-setup.conf.example): This file is crucial for customizing the Core Rule Set. Here, you can configure paranoia levels, anomaly score thresholds, and other CRS-specific behaviors. You should copy this file and tailor it to your application's needs.

```yaml
name: waf-controller
kind: WAFController
ruleGroups:
- name: sqlinjection
  rules:
    customRules: |
      // https://github.com/corazawaf/coraza-coreruleset/blob/main/rules/%40crs-setup.conf.example
      // The crs-setup.conf file is required for CRS to function.
      // This is a minimal example; a full setup file offers more customization.
      SecRequestBodyAccess On
      SecDefaultAction "phase:1,log,auditlog,pass"
      SecDefaultAction "phase:2,log,auditlog,pass"
      SecAction \
          "id:900990,\
          phase:1,\
          pass,\
          t:none,\
          nolog,\
          tag:'OWASP_CRS',\
          ver:'OWASP_CRS/4.16.0',\
          setvar:tx.crs_setup_version=4160"
    owaspRules:
    - REQUEST-901-INITIALIZATION.conf
    - REQUEST-913-SCANNER-DETECTION.conf
    - REQUEST-921-PROTOCOL-ATTACK.conf
    - REQUEST-922-MULTIPART-ATTACK.conf
    - REQUEST-949-BLOCKING-EVALUATION.conf
```

For all rules, please check [here](https://github.com/corazawaf/coraza-coreruleset/tree/main/rules/%40owasp_crs).

### IP Blocker

The `ipBlocker` rule filters requests based on the source IP address, specified in `CIDR` format.

#### Whitelist Mode

Logic: Allows requests only from IPs on the list. All others are blocked.

```yaml
kind: WAFController
name: waf-controller
ruleGroups:
  - name: ipblocker
    rules:
      ipBlocker:
        whitelist:
          - 136.252.0.2/32
```

#### Blacklist Mode

Logic: Blocks requests from IPs on the list. All others are allowed.

```yaml
kind: WAFController
name: waf-controller
ruleGroups:
  - name: ipblocker
    rules:
      ipBlocker:
        blacklist:
          - 158.160.2.1/32
```

#### Combined Mode (Whitelist & Blacklist)

Logic: Blocks request if IP is in blacklist OR not in whitelist.

```yaml
kind: WAFController
name: waf-controller
ruleGroups:
  - name: ipblocker
    rules:
      ipBlocker:
        whitelist:
          - 136.252.0.2/32
        blacklist:
          - 158.160.2.1/32
```

### GEO IP Blocker

The `geoIPBlocker` rule allows or denies requests based on the visitor's country of origin. It requires a GeoIP database to function.

- `dbPath`: The file path to your GeoIP database (.mmdb file).
- `dbUpdateCron`: (Optional) A cron expression to schedule automatic updates for the database.
- `Country Codes`: Use standard two-letter ISO codes (e.g., US, CN).

#### Whitelist Mode (allowedCountries)

Logic: Only allows requests from the specified countries. All other countries will be blocked.

```yaml
kind: WAFController
name: waf-controller
ruleGroups:
  - name: geoipblocker
    rules:
      geoIPBlocker:
        dbPath: <your-dir>/Country.mmdb
        allowedCountries:
          - XX
```

#### Blacklist Mode (deniedCountries)

Logic: Blocks requests from the specified countries. All other countries will be allowed.

```yaml
kind: WAFController
name: waf-controller
ruleGroups:
  - name: geoipblocker
    rules:
      geoIPBlocker:
        dbPath: <your-dir>/Country.mmdb
        dbUpdateCron: "0 0 1 * *"
        deniedCountries:
          - XX
```

### Rate Limiter

The RateLimiter restricts how many requests a client can make within a specific time window (e.g., requests per second). This is a crucial defense against brute-force attacks and helps prevent system overload from excessive traffic.

For detailed configuration, please refer to the official documentation for the built-in Easegress RateLimiter filter [here](./../07.Reference/7.02.Filters.md#ratelimiter).

## Observability

To monitor requests blocked by the WAF, you can use the `waf_total_refused_requests` counter metric exposed to Prometheus. This metric increments every time a request is denied by a WAF rule.

It includes several labels to help you pinpoint the cause of the block:

Common Labels:

- `kind`, `clusterName`, `clusterRole`, `instanceName`: Standard labels to identify the Easegress instance.

Metric-Specific Labels:

- `ruleGroup`: The name of the rule group that blocked the request.
- `ruleID`: The ID of the specific rule that was triggered.
- `action`: The WAF action, canbe one of `drop`, `deny`, `redirect`.

Using these labels, you can create detailed dashboards and alerts, for instance, to track which rules are most active or to identify repeated block events from a specific source.

For a complete list of all available metrics, please see the [Metrics](../07.Reference/7.08.Metrics.md) reference documentation.

---

### 02.Tutorials 2.1.Egctl Usage (`docs/02.Tutorials/2.1.egctl-Usage.md`)

# 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.

## 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] \
 [--auto-cert-email EMAIL] \
 [--auto-cert-domain-name DOMAIN_NAME] \
 [--dns-provider KEY=VALUE] \
 [--dns-provider KEY2=VALUE2]
```

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
```

You can use the `egctl create httpproxy` command to create or update an `AutoCertManager`. Below is an example command and its equivalent YAML configuration.

For example:

```bash
egctl create httpproxy demo2 --port 10081 \
 --rule="/bar=http://127.0.0.1:8083" \
 --auto-cert \
 --auto-cert-email someone@megaease.com \
 --auto-cert-domain-name="*.foo.com" \
 --dns-provider name=dnspod \
 --dns-provider zone=megaease.com \
 --dns-provider="apiToken=<tokenvalue>"
```

Parameters:

- `auto-cert-email`: Required for creating a new `AutoCertManager`. If an `AutoCertManager` exists, this updates its email field.
- `auto-cert-domain-name` and `dns-provider`: Specify the domain in the `AutoCertManager` config. This either appends to or updates the domains field of `AutoCertManager`.

Equivalent YAML Configuration

```yaml
kind: AutoCertManager
name: AutoCertManager
email: someone@megaease.com
domains:
  - name: "*.megaease.com"
    dnsProvider:
      name: dnspod
      zone: megaease.com
      apiToken: <token value>
```

## 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"
```

In Easegress, when using `IngressController`, `MeshController`, or `GatewayController`, resources such as `HTTPServers` and `Pipelines` are created in separate namespaces to prevent conflicts. To access these resources, you can utilize the `--namespace` or `--all-namespaces` argument.

> The use of these arguments is relevant only when employing `IngressController`, `MeshController`, or `GatewayController`. Easegress does not support creating resources in different namespaces by using apis or `egctl` command.

```bash
egctl get all --all-namespaces         # view all resources in all namespace.
egctl get all --namespace default      # view all resources in default namespace.
egctl describe pipeline --all-namespaces           # describe pipeline resources in all namespace.
egctl describe httpserver demo --namespace default # describe httpserver demo in default namespace.
```

## 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 ai enable                        # enable AI Gateway feature

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 & Security

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 <name>  # update the current-context field in the .egctlrc file to <name>
egctl config view                # display the contents of the configuration file
```

---

### 02.Tutorials 2.2.HTTP Proxy Usage (`docs/02.Tutorials/2.2.HTTP-Proxy-Usage.md`)

# 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 

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: <public-key-data>
  cert2: <public-key-data>
# Corresponding private keys
keys: 
  cert1: <private-key-data>
  cert2: <private-key-data>

# Max request body size; default is 4MB
clientMaxBodySize: 4 * 1024 * 1024

# Max connections with clients, default 10240
maxConnections: 10240

# Root certificate authorities
caCertBase64: <your-ca-cert>

# 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: <your-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.01.Controllers.md#httpserver)
- [AutoCertManager](../07.Reference/7.01.Controllers.md#autocertmanager)
- [GlobalFilter](../07.Reference/7.01.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:

```
/* Detailed source-code truncated for AI context efficiency. */
```

### 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.02.Filters.md#proxy).
- `Other Filters`: Easegress offers a multitude of filters for various purposes. To explore them, visit [Filters](../07.Reference/7.02.Filters.md).

---

### 02.Tutorials 2.3.Pipeline Explained (`docs/02.Tutorials/2.3.Pipeline-Explained.md`)

# Pipeline Explained 

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)
  - [Health Check](#health-check)
  - [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: buildFailureResponse
  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`. For more information, please check [load balance](../07.Reference/7.02.Filters.md#proxyloadbalancespec).


```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
```

### Health Check
Perform a health check on the servers in the pool. If a server fails the check, it will be marked as unhealthy, and requests will be rerouted to other healthy servers until it regains health.

More details config in [Proxy](../07.Reference/7.02.Filters.md#health-check).

```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
      healthCheck:
        # interval between health checks (default: 60s)
        interval: 60s

        # uri for health check http request
        uri: /health
        # http request headers for health check
        headers:
          X-Health-Check: easegress
        # username for basic authentication
        username: admin
        # password for basic authentication
        password: xxxxxx

        # response validation criteria (default: 2xx and 3xx status codes)
        match:
          # acceptable status code ranges
          statusCodes:
          - [200, 299] # 2xx
          - [300, 399] # 3xx
```

### 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.02.Filters.md#requestbuilder) & [ResponseBuilder](../07.Reference/7.02.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. [Jumpif mechanism in pipeline](../06.Development-for-Easegress/6.1.Developer-Guide.md#jumpif-mechanism-in-pipeline)

---

### 02.Tutorials 2.4.Resilience (`docs/02.Tutorials/2.4.Resilience.md`)

# Resilience 

- [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.01.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: 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.01.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: 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

---

### 02.Tutorials 2.5.Traffic Verification (`docs/02.Tutorials/2.5.Traffic-Verification.md`)

# Security 

- [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)

## 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. [signer Literal](../07.Reference/7.02.Filters.md#signerliteral)
4. https://oauth.net/2/
5. [validator OAuth2JWT](../07.Reference/7.02.Filters.md#validatoroauth2jwt)
6. https://en.wikipedia.org/wiki/Basic_access_authentication
7. https://manpages.debian.org/testing/apache2-utils/htpasswd.1.en.html

---

### 02.Tutorials 2.6.Websocket (`docs/02.Tutorials/2.6.Websocket.md`)

# WebSocket 

- [Background](#background)
- [Design](#design)
- [Health Check](#health-check)
  - [Health Check](#health-check-1)
- [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 `nhooyr.io/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
      # keepHost: true, the `Host` will be the same as the original request
      # If the backend is a load balancer, it would prove to be highly beneficial
      keepHost: true
    - url: ws://127.0.0.1:9095
    - url: ws://127.0.0.1:9096
    - url: ws://127.0.0.1:9097
    loadBalance:
      policy: roundRobin
    # the max number of bytes to read for a single message in 
    # client/server connection.
    # default is 32769, set -1 to disable limit.
    clientMaxMsgSize: 32769
    serverMaxMsgSize: 32769
 ```

* 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.

## Health Check

### Health Check
Perform a health check on the servers in the pool. If a server fails the check, it will be marked as unhealthy, and requests will be rerouted to other healthy servers until it regains health. Health check for websocket proxy contains both http way or websocket way. The HTTP check involves a request-response evaluation similar to a Proxy filter. In the WebSocket method, a successful connection yields a 101 status code. Additional headers can be set and evaluated in both methods. 
If you send both two ways of health check, then a server passes both HTTP and WebSocket health checks, it will be considered healthy.

More details in [WebSocketProxy](../07.Reference/7.02.Filters.md#health-check-1).

```yaml
kind: WebSocketProxy
name: proxy-example-1
pools:
- servers:
  - url: ws://127.0.0.1:9095
  - url: ws://127.0.0.1:9096
  - url: ws://127.0.0.1:9097
  healthCheck:
    # interval between health checks (default: 60s)
    interval: 60s
    # timeout for health check response (default: 3s)
    timeout: 3s
    # fail threshold to mark server as unhealthy (default: 1)
    fails: 1
    # success threshold to mark server as healthy (default: 1)
    pass: 1

    ws:
      # health check request port (defaults to server's port, e.g., 9095)
      port: 10080
      # uri for health check
      uri: /health/ws
      # http request headers for health check
      headers:
        X-Health-Check: easegress

      # response validation criteria (default: 101 Switching Protocols)
      match:
        # acceptable status code ranges 
        statusCodes:
        - [101, 101] # 101
        # response header validation
        # name is header key.
        # value is header value, can be empty. 
        # type is type of value, can be "exact" or "regexp".
        headers:
        - name: X-Status
          value: healthy
          type: exact
    http:
      uri: /health
```

## 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. <https://datatracker.ietf.org/doc/html/rfc6455>
2. <https://www.nginx.com/blog/websocket-nginx/>
3. <https://docs.oracle.com/en-us/iaas/Content/Balance/Reference/httpheaders.htm>

---

### 02.Tutorials 2.7.GRPC (`docs/02.Tutorials/2.7.gRPC.md`)

# gRPC 

- [GRPCServer](#grpcserver)
  - [Example Requests](#example-requests)
- [GRPCProxy](#grpcproxy)

## 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.

``` yaml
name: server-grpc
kind: GRPCServer
port: 8080

# 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:
# Rules for host matching.
# If not match, GRPCServer will check next rule.
- host: <your-host>
  hostRegexp: <your-host-regexp>

  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.01.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.02.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.02.Filters.md#grpcproxy) filter.

---

### 02.Tutorials 2.8.HTTPS Lets Encrypt (`docs/02.Tutorials/2.8.HTTPS-Lets-Encrypt.md`)

# HTTPS and Let's Encrypt 

- [Introduction](#introduction)
- [Configuring HTTPS in HTTPServer](#configuring-https-in-httpserver)
  - [Manual Configuration](#manual-configuration)
  - [Using AutoCertManager](#using-autocertmanager)
- [AutoCertManager Configuration](#autocertmanager-configuration)


## Introduction

Easegress offers both manual and automated approach to manage HTTPS certificates. The automated method is using business controller AutoCertManager. This tutorial will guide you through setting up HTTPS in HTTPServer and configuring AutoCertManager for certificate management with Let's Encrypt.

## Configuring HTTPS in HTTPServer

To implement HTTPS in HTTPServer, you have two options: manually providing certificates and keys, or using AutoCertManager for automated certificate management.

### Manual Configuration

For manual configuration, you need to set https to true and provide your certificates and keys.

```yaml
name: demo
kind: HTTPServer
https: true
autoCert: false
certs:
  cert1: <public-key-data>
  cert2: <public-key-data>
keys:
  cert1: <private-key-data>
  cert2: <private-key-data>
...
```

### Using AutoCertManager

If you prefer automated management with Let's Encrypt, set autoCert to true. This will utilize the AutoCertManager.

```yaml
name: demo
kind: HTTPServer
https: true
autoCert: true
...
```

## AutoCertManager Configuration

`AutoCertManager` is a business controller, which is more special than others. Because there can be at most one instance of AutoCerManager. It manages HTTPS certificates and handles challenge traffic from Let's Encrypt.

```yaml
kind: AutoCertManager
name: AutoCertManager
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: <token value>
```

Explanation of Fields:

- email: Email address for Let's Encrypt registration.
- directoryURL: CA directory URL (default: Let's Encrypt official one).
- renewBefore: Time before expiry to renew the certificate.
- enableHTTP01, enableTLSALPN01, enableDNS01: Challenge types to enable.
- domains: Domains to manage, along with their DNS provider configurations.

Assuming we have saved the config in `acm.yaml`, we could use this command to update global AutoCertManager.

```bash
$ egctl apply -f acm.aml
```

See more details about `AutoCertManager` in [here](../07.Reference/7.01.Controllers.md#autocertmanager).

---

### 02.Tutorials 2.9.AI Gateway (`docs/02.Tutorials/2.9.AI-Gateway.md`)

# AI Gateway 

- [Introduction](#introduction)
- [Configuring AI Gateway](#configuring-ai-gateway)
- [Observability](#observability)
- [Benchmark](#benchmark)

## Introduction

The AI Gateway enables seamless proxying of large language model (LLM) APIs following OpenAI specifications. We support providers such as OpenAI, DeepSeek, Anthropic and so on. See [Supported Providers](../07.Reference/7.01.Controllers.md#supported-providers) for the complete list.

Beyond the basic proxying capabilities, the AI Gateway offers advanced features such as response caching, and dynamic routing based on real-time statistics. These enhancements ensure optimal performance and flexibility when integrating with various AI services.

## Configuring AI Gateway

To set up the AI Gateway, we need to create an `AIGatewayController` resource. This resource will manage the configuration and lifecycle of the AI Gateway, including the supported LLM providers and any specific routing or caching rules.

We have subcommand `egctl ai` to manage the AI Gateway configuration easily.

```bash
$ egctl ai enable

# It is the short form of:
$ echo 'kind: AIGatewayController
name: AIGatewayController' | egctl create -f -
```

To disable the AI Gateway, we can use the following command:

```bash
$ egctl ai disable

# It is the short form of:
$ egctl delete AIGatewayController AIGatewayController
```

> NOTICE: The deletion of the AI Gateway will remove all associated configurations(like api keys). Please proceed with caution.

```bash
# To view the current configuration of the AI Gateway, we can use:
$ egctl get AIGatewayController AIGatewayController -o yaml

$ egctl ai edit # Edit the AIGatewayController resource in the default editor.

# It is the short form of:
$ egctl edit aigatewaycontroller AIGatewayController
```

Basic configuration of the AI Gateway can be done through the `AIGatewayController` resource. Here is an example configuration:

```yaml
kind: AIGatewayController
name: AIGatewayController
providers:
    - name: openai-provider
      providerType: openai
      baseURL: https://api.openai.com
      apiKey: sk-proj-openai-api-key # Replace with your OpenAI API key
    - name: deepseek-provider
      providerType: deepseek
      baseURL: https://api.deepseek.com
      apiKey: sk-deepseek-api-key # Replace with your DeepSeek API key
version: easegress.megaease.com/v2
```

Besides using `egctl ai edit` to modify the configuration, we can still save it as `aigateway.yaml` and apply it:

```bash
egctl apply -f aigateway.yaml
```

After adding one or more providers, we can add Filter AIGatewayProxy in `Pipeline` to proxy requests to the providers. Here is an example of a `Pipeline` configuration that uses the AI Gateway:

```bash
echo 'name: ai-gateway-pipeline
kind: Pipeline
filters:
  - name: ai-gateway-proxy
    kind: AIGatewayProxy
    providerName: deepseek-provider # one of the provider names in the controller to use
' | egctl create -f -

echo 'kind: HTTPServer
name: ai-gateway-server
port: 8080
rules:
  - paths:
    - pathPrefix: /
      backend: ai-gateway-pipeline' | egctl create -f -

# Now we can access the AI Gateway through the HTTP server
$ curl http://127.0.0.1:8080/v1/chat/completions \
    -H "Content-Type: application/json" \
    -d '{
    "model": "deepseek-chat",
    "messages": [
      {"role": "user", "content": "Hello, who are you?"}
    ],
    "max_tokens": 100
  }'

{"id":"8c8ca32d-b368-4797-8bdd-f824e260ea98","object":"chat.completion","created":1753173468,"model":"deepseek-chat","choices":[{"index":0,"message":{"role":"assistant","content":"Hello! 😊 I'm DeepSeek Chat, your AI assistant created by DeepSeek. I'm here to help answer your questions, provide information, and assist with anything you need—whether it's learning, problem-solving, or just having a friendly chat! How can I help you today? 🚀"},"logprobs":null,"finish_reason":"stop"}],"usage":{"prompt_tokens":9,"completion_tokens":64,"total_tokens":73,"prompt_tokens_details":{"cached_tokens":0},"prompt_cache_hit_tokens":0,"prompt_cache_miss_tokens":9},"system_fingerprint":"fp_8802369eaa_prod0623_fp8_kvcache"}
```

We also support [Anthropic Message API](https://docs.anthropic.com/en/api/messages) as client-side, which means you can use access all providers by Anthropic Message API. Internally we transform Anthropic Message API requests to the [OpenAI Chat Completion API](https://platform.openai.com/docs/api-reference/chat/create) requests, and responses are converted back to Anthropic format.

```bash
# Example using Anthropic Message API format
$ curl http://127.0.0.1:8080/v1/messages \
  -H "Content-Type: application/json" \
  -d '{
  "model": "deepseek-chat",
  "max_tokens": 100,
  "messages": [
    {
      "role": "user",
      "content": [
        {
          "type": "text",
          "text": "Hello, who are you?"
        }
      ]
    }
  ]
}'

{"id":"d857284e-80dc-479c-bdb2-527235969d96","content":[{"citations":null,"text":"Hello! 😊 I'm DeepSeek Chat, your AI assistant created by DeepSeek. I'm here to help answer your questions, provide information, and assist with anything you need—whether it's learning something new, solving problems, or just having a fun conversation!  \n\nHow can I help you today? 🚀","type":"text","signature":"","thinking":"","data":"","id":"","input":null,"name":"","content":{"OfWebSearchResultBlockArray":null,"error_code":"","type":"web_search_tool_result_error"},"tool_use_id":""}],"model":"deepseek-chat","role":"assistant","stop_reason":"end_turn","stop_sequence":"","type":"message","usage":{"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"input_tokens":9,"output_tokens":67,"server_tool_use":{"web_search_requests":0},"service_tier":"standard"}}
```

## Observability

We can check the status of the AI Gateway providers using the following command:

```bash
$ egctl ai check
NAME       PROVIDER-TYPE HEALTHY
openai-provider  openai      YES
deepseek-provider deepseek     YES
```

We can also view the statistics of the AI Gateway, including the number of requests, success/failure rates, and average response times for each provider:

```bash
$ egctl ai stat
PROVIDER(TYPE) MODEL@BASEURL RESP-TYPE TOTAL-REQ SUCCESS/FAILED AVG-DUR(ms) TOKENS(INPUT/OUTPUT)
openai-provider(openai)  gpt-4o@https://api.openai.com  /v1/chat/completions 2  2/0  1842  26/72
deepseek-provider(deepseek) deepseek-chat@https://api.deepseek.com /v1/chat/completions 2  2/0  107  18/177
```

## Benchmark

In this benchmark, we compare popular open-source solutions providing AI gateway capabilities, including our own `Easegress`, as well as `Kong` and `APISIX`. Our goal is to provide clear, reproducible data to help users choose the right gateway for their scenarios.

### Test Environment

#### Machine

Benchmark machine: dual Intel Xeon Gold 5220R CPUs (96 cores, 192 threads), 251 GiB RAM.

#### Configs

See the benchmark scripts in [the benchmark scripts directory](../../scripts/benchmark/aigateway).

#### Mock LLM Server

To ensure fair comparison and reproducibility, we used a mock LLM server to simulate inference requests and responses. This allows us to focus on the gateway performance itself.

See the [mock server implementation](../../scripts/benchmark/aigateway/llm/).

### Benchmark Results

1000 requests with 50 concurrency:

| Name | QPS (Request/sec) | Latency (ms) |
| ---- | ----------------- | ------------ |
| Easegress | 11075 | 3.9 |
| APISIX | 10979 | 4.2 |
| Kong | 7528 | 6.2 |

10000 requests with 1000 concurrency:

| Name | QPS (Request/sec) | Latency (ms) |
| ---- | ----------------- | ------------ |
| Easegress | 13496 | 7.1 |
| APISIX | 15055 | 6.4 |
| Kong | 9061 | 10.7 |

The results demonstrate that `Easegress` delivers high throughput and low latency, outperforming `Kong` and matching or slightly trailing `APISIX` in certain high-concurrency scenarios.

See more details of the benchmark results in [the benchmark results directory](../../scripts/benchmark/aigateway/result/).

---

### 02.Tutorials 2.10.Claude Proxy (`docs/02.Tutorials/2.10.Claude-Proxy.md`)

# Claude Proxy with Easegress 

- [Overview](#overview)
- [Prerequisites](#prerequisites)
- [Setting Up the Claude Proxy](#setting-up-the-claude-proxy)
- [Using Claude with OpenAI-Compatible API](#using-claude-with-openai-compatible-api)
- [Troubleshooting](#troubleshooting)
- [Advanced Configuration](#advanced-configuration)

## Overview

The Claude Proxy feature in Easegress enables seamless integration between Anthropic's Claude AI models and systems designed for OpenAI's API. This powerful capability allows you to:

- Route AI requests through a consistent interface
- Access Claude's unique capabilities through familiar OpenAI endpoints

This guide walks you through setting up and using the Claude Proxy with Easegress.

## Prerequisites

Before proceeding, ensure you have:

- Easegress installed and running (see [Installation Guide](../01.Getting-Started/1.2.Install.md))
- An OpenAI API key for authentication
- The Claude CLI, follow official [claude code overview](https://docs.anthropic.com/en/docs/claude-code/overview) to install.

## Setting Up the Claude Proxy

The Claude Proxy requires configuring three components in Easegress:

1. **AIGatewayController**: Manages provider connections and API keys
2. **HTTPServer**: Exposes the proxy endpoints
3. **Pipeline**: Processes and routes the requests

Follow the detailed steps in the [AI Gateway Configuration](./2.9.AI-Gateway.md#configuring-ai-gateway) guide to set up these components. Once configured, any request with the path prefix `/v1/messages` will be automatically handled in Claude proxy mode.

Here's a complete example configuration:

```yaml
# HTTPServer configuration - handles incoming requests
- kind: HTTPServer
  name: ai-gateway-server
  port: 8080  # Change this if port 8080 is already in use
  rules:
    - paths:
        - backend: ai-gateway-pipeline
          pathPrefix: /  # Routes all requests to the AI gateway pipeline

---
# Pipeline configuration - processes the requests
- kind: Pipeline
  name: ai-gateway-pipeline
  filters:
    - kind: AIGatewayProxy
      name: ai-gateway-proxy
      providerName: openai-provider  # Must match the provider name in AIGatewayController

---
# AIGatewayController configuration - manages providers
kind: AIGatewayController
name: AIGatewayController
providers:
    - apiKey: sk-proj-openai-api-key  # Replace with your actual OpenAI API key
      baseURL: https://api.openai.com
      name: openai-provider
      providerType: openai
```

After applying this configuration, your Claude Proxy will be ready to accept requests at `http://localhost:8080`.

## Set up the Claude Environment

To use the Claude CLI with your proxy, you need to set environment variables that redirect Claude to use your Easegress proxy instead of the default Anthropic endpoints:

```bash
# Configure Claude to use your local Easegress proxy
export ANTHROPIC_BASE_URL=http://localhost:8080

# Map Claude models to OpenAI model names
# Note: Choose appropriate OpenAI models that match your use case
export ANTHROPIC_SMALL_FAST_MODEL="gpt-3.5-turbo"  # For faster, smaller tasks
export ANTHROPIC_MODEL="gpt-4"  # For more complex reasoning

# Start the Claude CLI
claude
```

> **Important**: Claude may use significantly more tokens than OpenAI for certain tasks, especially when processing large contexts. Consider this when setting up rate limits or budgets.

## Using Claude with OpenAI-Compatible API

After setting up the environment and launching Claude, you can verify the proxy connection is working:

1. Check the welcome message confirms the correct API Base URL:

```plain
Overrides (via env):
    • API Base URL: http://localhost:8080
```

1. Test with a simple query:

```bash
> who are you
⏺ Claude Code
```

You can now use Claude through the OpenAI-compatible API interface for various tasks:

- Code generation and analysis
- Text completion and summarization
- Question answering
- Document analysis
- Creative writing

## Troubleshooting

If you encounter issues with your Claude Proxy setup:

- **Connection errors**: Verify Easegress is running and the port (8080 by default) is available
- **Authentication failures**: Check that your OpenAI API key is correctly configured in the AIGatewayController
- **Model errors**: Ensure you're specifying model names that exist in OpenAI's API
- **Unexpected responses**: Check Easegress logs for detailed error information

---

### 02.Tutorials 2.11.Nginx Header Vars (`docs/02.Tutorials/2.11.Nginx-Header-Vars.md`)

# Nginx-Style Header Variables

Easegress HTTPServer supports nginx-style variables in the `setHeaders` configuration, allowing you to dynamically populate HTTP headers with request information. This feature provides powerful capabilities for debugging, logging, routing decisions, and passing contextual information to backend services.

## Overview

The nginx variable system in Easegress supports:

- **Request line variables** (method, URI, query parameters)
- **Host and server variables**
- **Client information** (remote address, user agent)
- **Time and timestamp variables**
- **Content and protocol information**
- **HTTP header extraction**
- **Query parameter extraction**
- **Cookie value extraction**
- **Default value support** with `${variable:default}` syntax
- **String interpolation** for building complex values

Easegress adopts nginx variables as much as possible for compatibility. For a complete reference of nginx variables, see the [official nginx variable index](https://nginx.org/en/docs/varindex.html).

## Variable Syntax

Easegress supports two nginx-compatible variable syntaxes:

```yaml
# Simple variable reference
X-Method: "$request_method"

# Variable with default value
X-Version: "${arg_version:v1.0}"

# String interpolation
X-Backend-URL: "${scheme}://${host}-backend${uri}"
```

## Complete Example

See the complete example configuration at: [`example/config/test-nginx-header-vars.yaml`](../../example/config/test-nginx-header-vars.yaml)

This example demonstrates all available variable types and usage patterns.

## Variable Categories

### 1. Request Line Variables

Extract information from the HTTP request line:

| Variable | Description | Example Value |
|----------|-------------|---------------|
| `$request_method` | HTTP method | `GET`, `POST`, `PUT` |
| `$request_uri` | Full request URI with query string | `/api/users?page=1` |
| `$uri` | Path portion of URI (without query) | `/api/users` |
| `$scheme` | Protocol scheme | `http`, `https` |
| `$query_string` | Query string portion | `page=1&limit=10` |
| `$args` | Alias for `$query_string` | `page=1&limit=10` |
| `$request` | Full request line | `GET /api/users HTTP/1.1` |

### 2. Host and Server Variables

Information about the server and request host:

| Variable | Description | Example Value |
|----------|-------------|---------------|
| `$host` | Host from request (without port) | `api.example.com` |
| `$hostname` | Alias for `$host` | `api.example.com` |
| `$http_host` | Host header value (with port) | `api.example.com:8080` |
| `$server_name` | Server name | `api.example.com` |
| `$server_protocol` | HTTP protocol version | `HTTP/1.1`, `HTTP/2.0` |

### 3. Client Information Variables

Information about the client making the request:

| Variable | Description | Example Value |
|----------|-------------|---------------|
| `$remote_addr` | Client IP address | `192.168.1.100` |
| `$remote_user` | Authenticated username | `john_doe` |
| `$proxy_add_x_forwarded_for` | X-Forwarded-For with client IP | `10.0.0.1, 192.168.1.100` |

### 4. Time and Timestamp Variables

Current time in various formats:

| Variable | Description | Example Value |
|----------|-------------|---------------|
| `$time_iso8601` | ISO 8601 timestamp | `2025-09-05T10:30:45+08:00` |
| `$time_local` | Local time format | `05/Sep/2025:10:30:45 +0800` |
| `$msec` | Timestamp in milliseconds | `1725508245123` |
| `$request_time` | Request processing time | `0.045` |

### 5. Content and Protocol Variables

Information about request content:

| Variable | Description | Example Value |
|----------|-------------|---------------|
| `$content_type` | Content-Type header | `application/json` |
| `$content_length` | Content-Length header | `1024` |
| `$request_length` | Total request size | `1024` |

### 6. HTTP Header Variables

Extract any HTTP header using the `$http_` prefix:

| Variable Pattern | Description | Example |
|------------------|-------------|---------|
| `$http_user_agent` | User-Agent header | `Mozilla/5.0 (...)` |
| `$http_accept` | Accept header | `application/json` |
| `$http_x_forwarded_for` | X-Forwarded-For header | `10.0.0.1` |
| `$http_authorization` | Authorization header | `Bearer token123` |
| `$http_*` | Any header (replace * with header name) | Various |

**Note:** Header names are converted using these rules:

- Convert to lowercase
- Replace hyphens with underscores
- Add `http_` prefix

Examples:

- `Content-Type` → `$http_content_type`
- `X-API-Key` → `$http_x_api_key`
- `User-Agent` → `$http_user_agent`

### 7. Query Parameter Variables

Extract query parameters using the `$arg_` prefix:

| Variable Pattern | Description | Example |
|------------------|-------------|---------|
| `$arg_page` | Extract `page` parameter | `1` |
| `$arg_limit` | Extract `limit` parameter | `50` |
| `$arg_api_key` | Extract `api_key` parameter | `abc123` |
| `$arg_*` | Any query parameter | Various |

Example URL: `https://api.example.com/users?page=2&limit=50&api_key=abc123`

### 8. Cookie Variables

Extract cookie values using the `$cookie_` prefix:

| Variable Pattern | Description | Example |
|------------------|-------------|---------|
| `$cookie_session_id` | Extract `session_id` cookie | `sess_abc123` |
| `$cookie_user_pref` | Extract `user_pref` cookie | `dark_mode` |
| `$cookie_*` | Any cookie value | Various |

### 9. Path Parameter Variables

Extract path parameters from route patterns (when using parameterized routes):

| Variable | Description | Example |
|----------|-------------|---------|
| `$id` | Path parameter `id` | `123` |
| `$name` | Path parameter `name` | `john` |
| Custom names | Based on route definition | Various |

## Default Values

Use the `${variable:default}` syntax to provide fallback values:

```yaml
setHeaders:
  X-Version: "${arg_version:v1.0}"          # Default to "v1.0" if no version param
  X-User: "${remote_user:anonymous}"        # Default to "anonymous" if not authenticated
  X-Content-Type: "${content_type:none}"    # Default to "none" if no content type
```

## Complex String Building

Combine multiple variables to build complex header values:

```yaml
setHeaders:
  # Build backend URL
  X-Backend-URL: "${scheme}://${host}-backend${uri}"

  # Client information summary
  X-Client-Info: "IP: ${remote_addr}, Agent: ${http_user_agent:unknown}"

  # Request summary
  X-Request-Summary: "Method=${request_method}, Host=${host}, Path=${uri}, Client=${remote_addr}"

  # Proxy chain information
  X-Proxy-Chain: "Forwarded: ${proxy_add_x_forwarded_for}"

  # Processing information
  X-Processing-Time: "Started at ${time_iso8601} using ${server_protocol}"
```

## Usage Examples

### Basic Request Information

```yaml
kind: HTTPServer
name: basic-vars-demo
port: 10080
rules:
  - paths:
    - pathPrefix: /
      backend: my-backend
      setHeaders:
        X-Method: "$request_method"
        X-Path: "$uri"
        X-Client-IP: "$remote_addr"
        X-Timestamp: "$time_iso8601"
```

### API Gateway Headers

```yaml
setHeaders:
  # API versioning
  X-API-Version: "${arg_version:v1}"
  X-API-Key: "${http_x_api_key:none}"

  # Request tracking
  X-Request-ID: "${http_x_request_id:auto-generated}"
  X-Trace-ID: "${http_x_trace_id:${time_msec}}"

  # Client information
  X-Client-IP: "$remote_addr"
  X-User-Agent: "$http_user_agent"
  X-Forwarded-For: "$proxy_add_x_forwarded_for"
```

### Debugging Headers

```yaml
setHeaders:
  # Full request debugging
  Debug-Method: "$request_method"
  Debug-URI: "$request_uri"
  Debug-Query: "$query_string"
  Debug-Host: "$http_host"
  Debug-Content-Type: "${content_type:none}"
  Debug-Content-Length: "${content_length:0}"
  Debug-User-Agent: "$http_user_agent"
  Debug-Request-Time: "$request_time"
```

### Load Balancer Headers

```yaml
setHeaders:
  # Upstream information
  X-Upstream-Server: "${scheme}://${host}"
  X-Original-Host: "$http_host"
  X-Original-URI: "$request_uri"

  # Load balancing info
  X-LB-Method: "roundrobin"
  X-LB-Timestamp: "$time_iso8601"
  X-Client-Real-IP: "$remote_addr"
```

## Testing Variables

To test your nginx variables configuration:

1. **Apply the configuration:**

   ```bash
   egctl create -f example/config/test-nginx-header-vars.yaml
   ```

2. **Send test requests:**

   ```bash
   # Basic request
   curl -H "User-Agent: MyApp/1.0" \
        -H "X-API-Key: test123" \
        "http://localhost:10080/api/users?page=1&debug=true"

   # Request with cookies
   curl -H "Cookie: session_id=sess_abc123; user_pref=dark_mode" \
        "http://localhost:10080/profile"

   # POST request with content
   curl -X POST \
        -H "Content-Type: application/json" \
        -d '{"name":"test"}' \
        "http://localhost:10080/api/create"
   ```

3. **Check backend logs** to see the populated header values.

## Best Practices

### 1. Use Default Values

Always provide sensible defaults for variables that might not be present:

```yaml
X-Version: "${arg_version:v1.0}"       # Good
X-Version: "$arg_version"              # Bad - might be empty
```

### 2. Sanitize Sensitive Information

Be careful with variables that might contain sensitive data:

```yaml
# Good - mask sensitive tokens
X-Auth-Present: "${http_authorization:false}"

# Bad - might expose sensitive data
X-Auth-Token: "$http_authorization"
```

### 3. Use Descriptive Header Names

Choose clear, descriptive header names:

```yaml
X-Client-IP: "$remote_addr"            # Good
X-IP: "$remote_addr"                   # Less clear
```

### 4. Group Related Headers

Organize headers logically:

```yaml
setHeaders:
  # Client information
  X-Client-IP: "$remote_addr"
  X-Client-Agent: "$http_user_agent"

  # Request information
  X-Request-Method: "$request_method"
  X-Request-Path: "$uri"

  # Timing information
  X-Request-Time: "$time_iso8601"
```

## Common Use Cases

### 1. Microservices Communication

Pass request context to downstream services:

```yaml
setHeaders:
  X-Original-Method: "$request_method"
  X-Original-URI: "$request_uri"
  X-Client-IP: "$remote_addr"
  X-Request-ID: "${http_x_request_id:${msec}}"
```

### 2. Security Headers

Add security and tracking information:

```yaml
setHeaders:
  X-Forwarded-Proto: "$scheme"
  X-Real-IP: "$remote_addr"
  X-Forwarded-For: "$proxy_add_x_forwarded_for"
  X-Request-Start: "$msec"
```

### 3. API Gateway

Standardize API request information:

```yaml
setHeaders:
  X-API-Gateway: "easegress"
  X-API-Version: "${arg_version:v1}"
  X-Client-ID: "${http_x_client_id:anonymous}"
  X-Rate-Limit-Key: "${remote_addr}-${http_x_api_key:none}"
```

### 4. Debugging and Monitoring

Add comprehensive debugging information:

```yaml
setHeaders:
  X-Debug-Method: "$request_method"
  X-Debug-Path: "$uri"
  X-Debug-Query: "$query_string"
  X-Debug-Client: "$remote_addr"
  X-Debug-Timestamp: "$time_iso8601"
  X-Debug-Protocol: "$server_protocol"
```

## See Also

- [HTTP Proxy Usage](2.2.HTTP-Proxy-Usage.md) - Basic HTTP proxy configuration
- [Pipeline Explained](2.3.Pipeline-Explained.md) - Understanding Easegress pipelines
- [Complete example configuration](../../example/config/test-nginx-header-vars.yaml) - Full working example

---

### 01.Getting Started 1.1.Quick Start (`docs/01.Getting-Started/1.1.Quick-Start.md`)

# 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" \
  --rule="/prefix*=http://127.0.0.1:9097"
```

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.

```bash
curl -v 127.0.0.1:10080/prefix/123
```

The request will be forwarded to `127.0.0.1:9097/prefix/123`.

## YAML Configuration

The `egctl create httpproxy` command is convenient, but for production use, you'll want more control. Easegress uses two main resources: **HTTPServer** (handles incoming requests) and **Pipeline** (processes and routes requests).

### Method 1: HTTPServer with Built-in Backend Pool

This approach automatically creates pipelines for you:

```bash
$ echo 'kind: HTTPServer
name: demo-backend-pool
port: 10080
https: false
rules:
  - paths:
    - pathPrefix: /demo0
      backendPool:
        loadBalance:
          policy: roundRobin
        servers:
            - url: http://127.0.0.1:9091
            - url: http://127.0.0.1:9092
    - pathPrefix: /demo1
      backendPool:
        loadBalance:
          policy: roundRobin
        servers:
            - url: http://127.0.0.1:9093
            - url: http://127.0.0.1:9094' | egctl create -f -
```

It will generate one pipeline for each backendPool. For example, `demo-backend-pool` generates pipeline`GENERATED-demo-backend-pool-0-0` and `GENERATED-demo-backend-pool-0-1` which couldn't be updated or deleted via `egctl` (User APIs).

### Method 2: HTTPServer with Separate Pipelines

Create HTTPServer:

```bash
$ echo '
kind: HTTPServer
name: demo-backend
port: 10081
https: false
rules:
  - paths:
    - path: /pipeline
      backend: demo-0
    - pathPrefix: /prefix
      backend: demo-1 ' | egctl create -f -
```

Create the Pipelines:

```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
---
name: demo-1
kind: Pipeline
flow:
  - filter: proxy
filters:
  - name: proxy
    kind: Proxy
    pools:
    - servers:
      - url: http://127.0.0.1:9097' | 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.

---

### 01.Getting Started 1.2.Install (`docs/01.Getting-Started/1.2.Install.md`)

# Install

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
    ```

### Guided Installation via KubeStellar Console

[KubeStellar Console](https://console.kubestellar.io) offers a
[guided installation mission for Easegress](https://console.kubestellar.io/missions/install-easegress)
that walks you through the setup step by step. The guided experience includes
pre-flight checks, installation validation, troubleshooting tips, and rollback
instructions.

---

### 01.Getting Started 1.3.Concepts (`docs/01.Getting-Started/1.3.Concepts.md`)

# Concepts

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.01.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.02.Filters.md).

---

--- METRICS ---
- Files Extracted: 56
- Estimated Token Budget: ~126760 tokens
- Canonical Reference: https://codewiki.google/github.com/easegress-io/easegress
