
Um proxy reverso como o nginx, construído sobre pingora, simples e eficiente.
# Pingap
Um proxy reverso e gateway de aplicações construído sobre o [Cloudflare Pingora](https://github.com/cloudflare/pingora). É configurado através de ficheiros TOML ou de uma interface web de administração que faz parte do binário, aplica alterações sem reiniciar e vem com mais de trinta plugins.
[中文说明](https://github.com/vicanso/pingap/blob/main/README_zh.md) | [Documentation](https://pingap.io/) · [中文文档](https://pingap.io/zh/) | [Examples](https://github.com/vicanso/pingap/blob/main/examples/README.md) | [Plugins](https://github.com/vicanso/pingap/blob/main/pingap-plugin/README.md) | [Crates](https://github.com/vicanso/pingap/blob/main/docs/README.md) | [Upgrading](https://pingap.io/guide/upgrading)

## O que o distingue
- **Uma interface web de administração no binário.** Cada definição tem um formulário. Uma alteração é verificada da mesma forma que `pingap -t` a verifica antes de ser guardada, versões anteriores podem ser mantidas e restauradas, e existem contas só de leitura e tokens de API. A sua página inicial apresenta gráficos do tráfego dos últimos quinze minutos - taxa, estado, latência, cada upstream - sem Prometheus.
- **Alterações sem tempo de inatividade.** Upstreams, locations, plugins e certificados são substituídos no processo em execução (`--autoreload`). O que exige novos listeners é aplicado por um reinício que transfere os sockets (`--autorestart`, para um daemon no Linux), e o processo em execução continua a servir se o seu substituto não arrancar.
- **Mais de trinta plugins**, por location e pela ordem em que os listar: autenticação (JWT, OIDC, key, basic, HMAC, forward auth), regras de acesso (IP, referer, user agent, caminhos bloqueados), tráfego (limites de taxa e largura de banda, cache, mirroring, divisão de tráfego), conteúdo (compressão, substituição de corpo, redirecionamentos, ficheiros estáticos, otimização de imagens) e operações (modo de manutenção, endpoint de saúde, páginas de erro). [Todos os plugins](https://github.com/vicanso/pingap/blob/main/pingap-plugin/README.md)
- **Certificados sem cron job.** Let's Encrypt ou qualquer outra CA ACME, por HTTP-01 ou DNS-01 (Alibaba Cloud, Cloudflare, Tencent Cloud, Huawei Cloud), RSA e ECDSA lado a lado, OCSP stapling, e mutual TLS para com os clientes e para com os upstreams.
- **Configuração verificada antes de ser executada.** `-t` constrói tudo o que um arranque construiria, `--strict` recusa chaves que não conhece, `--diff` mostra o que uma alteração faria. TOML, HCL ou KDL, em ficheiros ou em etcd, com segredos obtidos do ambiente ou de ficheiros.
- **Feito para ser observado.** Métricas Prometheus (pull e push), traces OpenTelemetry, logs de acesso em texto ou JSON com mais de trinta campos, e impressões digitais TLS JA4 dos clientes.
O resto do que um proxy reverso faz também está presente: HTTP/1.1, HTTP/2, WebSocket e gRPC-web; encaminhamento por host, caminho e expressão regular; round robin, least connections, consistent hashing e sessões sticky, com verificações de saúde, retries e um circuit breaker; backends a partir de DNS (A/AAAA, SRV) ou de labels Docker; o protocolo PROXY nos listeners e para com os upstreams; OpenSSL ou rustls.
Métricas, tracing e otimização de imagens estão nas builds `-full`.
## 🚀 Começar
### Docker Compose
```yaml
# docker-compose.yml
services:
pingap:
image: vicanso/pingap:latest # for production, pin a release: vicanso/pingap:<version>-full
container_name: pingap-instance
restart: always
ports:
- "80:80"
- "443:443"
volumes:
# everything Pingap keeps: configuration, certificates, cache
- ./pingap_data:/opt/pingap
environment:
- PINGAP_CONF=/opt/pingap/conf
- PINGAP_ADMIN_ADDR=0.0.0.0:80/pingap
- PINGAP_ADMIN_USER=pingap
- PINGAP_ADMIN_PASSWORD=<YourSecurePassword> # change this
command:
- pingap
- --autoreload
```
```bash
mkdir pingap_data
docker compose up -d
```
O admin está em http://localhost/pingap. As imagens são marcadas como `latest`, `full` e `rustls-full`, e `<version>`, `<version>-full`, `<version>-rustls-full` para uma versão. Cada uma também tem uma variante `-distroless` sem shell, onde `command` precisa começar com `pingap` como acima.
### Binário
```bash
curl -sSL https://raw.githubusercontent.com/vicanso/pingap/main/install.sh | sh
```
instala a versão mais recente em `/usr/local/bin/pingap` no Linux e macOS (x86_64 e arm64). As variáveis antes de `sh` escolhem o que é instalado:
| Variável | Efeito |
| --- | --- |
| `PINGAP_FULL=1` | A build `-full`, com todos os recursos opcionais |
| `PINGAP_TLS=rustls` | Linux: a build `-rustls-full`, sem OpenSSL (veja [TLS backend](#tls-backend)) |
| `PINGAP_LIBC=gnu` | Linux: a build glibc em vez da musl estática |
| `PINGAP_SERVICE=1` | Linux com systemd: também o serviço `pingap` e uma configuração inicial em `/etc/pingap/conf`; ele não é habilitado nem iniciado |
| `PINGAP_VERSION=0.15.0` | Essa versão em vez da mais recente |
Todos os assets estão na [página de releases](https://github.com/vicanso/pingap/releases).
### Um comando, sem arquivo de configuração
```bash
# the certificate comes from Let's Encrypt
pingap --domain=pingap.io --upstream=192.168.1.1:3000
# or bring your own
pingap --domain=pingap.io --upstream=192.168.1.1:3000 --cert=/etc/ssl/pingap.io
```
- Sem `--cert`, o certificado é solicitado via HTTP-01: o domínio precisa resolver para este host, com a porta 80 acessível. Ele é mantido em `~/.pingap/acme/<domains>.toml` e reutilizado na próxima inicialização. Não o exclua, a emissão é limitada por taxa.
- `--cert` é o certificado ou o diretório em que ele está (`fullchain.pem` / `privkey.pem`, `cert.pem` / `key.pem` e `tls.crt` / `tls.key` são encontrados; `--key` nomeia qualquer outro). `--upstream` e `--domain` recebem listas separadas por vírgulas, `--addr` o endereço no qual escutar.
- A configuração é criada novamente a cada inicialização, portanto o administrador não pode editá-la. Para mais de um servidor, use `--conf`, que não pode ser combinado com estas flags.
## 📝 Configuração
```toml
[upstreams.api]
addrs = ["api.github.com:443"]
discovery = "dns"
sni = "api.github.com"
[plugins.staticServe]
category = "directory"
path = "~/Downloads"
[locations.github-api]
upstream = "api"
path = "/api"
proxy_set_headers = ["Host:api.github.com"]
rewrite = "^/api/(?<path>.+)$ /$1"
[locations.static]
plugins = ["staticServe"]
[servers.test]
addr = "127.0.0.1:6118"
locations = ["github-api", "static"]
```
O mesmo pode ser escrito em HCL ou KDL. Cada chave está descrita na [referência de configuração](https://pingap.io/crates/config).
| | |
| --- | --- |
| `--autoreload` | Uma alteração é aplicada sem reinício: em até dez segundos a partir de ficheiros, de imediato a partir do etcd. O que exige reinício é reportado e aguarda por ele. O modo para contentores |
| `-a`, `--autorestart` | O mesmo, e um reinício que não descarta nenhum pedido para o que um reload não consegue alterar, como um endereço de escuta. Para um daemon (`-d`) no Linux; em qualquer outro lugar faz reload e reporta o que aguarda |
| `-t` | Constrói cada upstream, location, plugin, certificado e servidor como um arranque faz, e não liga nada |
| `--strict` | Uma chave que o Pingap não conhece, ou uma para a qual esta build não tem funcionalidade, é um erro, onde de outra forma seria um aviso. Para `-t` em CI |
| `--diff <other>` | Imprime o que mudaria se `<other>` tomasse o lugar da configuração, com as credenciais mostradas como checksums |
| `$ENV:NAME`, `$FILE:/path` | Como valor completo: substituído pela variável ou pelo ficheiro quando a configuração é carregada |
## 🔄 Como um pedido é tratado

1. O **servidor** aceita a ligação e escolhe a **location** cujo host e caminho melhor correspondem. Um pedido que não corresponda a nenhuma é respondido com `404`.
2. Os **plugins** da location executam-se pela ordem em que estão listados, cada um no seu próprio passo: à entrada, ou à saída. Um plugin pode responder ao pedido por si próprio - um acerto de cache, uma autenticação falhada, um redirecionamento, um ficheiro estático - e o upstream nunca é então consultado.
3. Caso contrário, o **upstream** da location escolhe um backend saudável e o pedido é encaminhado. Com `max_retries`, uma ligação que não pode ser estabelecida é tentada noutro backend; com `fallback_upstream`, um pedido que o upstream não consegue aceitar vai para um segundo upstream.
4. A resposta regressa através dos plugins de resposta ao cliente, e o pedido é **registado**: log de acesso, métricas e traces.
## 📊 Desempenho
Cerca de 147.000 pedidos por segundo para `/ping` numa única thread de um M4 Pro, com um percentil 99 de 116µs (`wrk`, 10 ligações, sem log de acesso).
<details>
<summary>A execução</summary>
```
wrk 'http://127.0.0.1:6118/ping' --latency
Running 10s test @ http://127.0.0.1:6118/ping
2 threads and 10 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 66.41us 23.67us 1.11ms 76.54%
Req/Sec 73.99k 2.88k 79.77k 68.81%
Latency Distribution
50% 67.00us
75% 80.00us
90% 91.00us
99% 116.00us
1487330 requests in 10.10s, 194.32MB read
Requests/sec: 147260.15
Transfer/sec: 19.24MB
```
</details>
## 🔧 Desenvolvimento
```bash
make dev # needs bacon: cargo install bacon
make build-web # after a change under web/, needs Node.js
make fmt && make lint && make test
```
`make dev` compila com as features `full`, executa o Pingap com a configuração em `~/tmp/pingap` com `--autoreload`, serve o admin em `127.0.0.1:3018` (`pingap` / `123123`) e recompila quando os fontes mudam. O admin é incorporado no binário a partir de `dist/`, que `make build-web` preenche. A versão mínima suportada do Rust é 1.96.
### Backend TLS
A compilação padrão usa OpenSSL, compilado a partir do código-fonte. Para rustls:
```bash
cargo build --release --no-default-features --features tls-rustls,full
```
Uma build rustls sempre oferece TLS 1.2 e 1.3 com os próprios conjuntos de cifras do rustls, portanto, `tls_min_version`, `tls_max_version`, `tls_cipher_list` e `tls_ciphersuites` por servidor são recusados quando a configuração é verificada. Todo o resto se comporta da mesma forma; as diferenças estão listadas com o [certificate crate](https://pingap.io/crates/certificate). `pingap --version` indica qual backend um binário possui.
## 🤝 Contribuindo
Pull requests são bem-vindos.
- Para um novo recurso, abra uma issue primeiro, para que possa ser discutido antes de qualquer código ser escrito.
- Por favor, não abra um pull request apenas para corrigir um erro de digitação ou a formatação de um comentário; estes são corrigidos em lotes.
- Execute `make fmt`, `make lint` e `make test` antes de enviar. O CI executa as mesmas verificações em cada pull request.
- Uma contribuição é feita sob o [Contributor License Agreement](https://github.com/vicanso/pingap/blob/main/CLA.md), que é aceito marcando a caixa correspondente no template do pull request: o trabalho continua sendo seu, e é licenciado ao projeto sob sua licença.
## 📄 Licença
[Apache License, Version 2.0](https://github.com/vicanso/pingap/blob/main/LICENSE).