
Un proxy inverso como nginx, construido sobre pingora, simple y eficiente.
# Pingap
Un proxy inverso y gateway de aplicaciones construido sobre [Cloudflare Pingora](https://github.com/cloudflare/pingora). Se configura mediante archivos TOML o a través de un panel de administración web que forma parte del binario, aplica los cambios sin reiniciar y viene con más de treinta plugins.
[中文说明](https://github.com/vicanso/pingap/blob/main/README_zh.md) | [Documentación](https://pingap.io/) · [中文文档](https://pingap.io/zh/) | [Ejemplos](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) | [Actualización](https://pingap.io/guide/upgrading)

## Qué lo distingue
- **Un panel de administración web en el binario.** Cada ajuste tiene un formulario. Un cambio se valida de la misma forma en que `pingap -t` lo comprueba antes de almacenarse, las versiones anteriores pueden conservarse y restaurarse, y existen cuentas de solo lectura y tokens de API. Su página de inicio muestra gráficas del tráfico de los últimos quince minutos: tasa, estado, latencia, cada upstream, sin Prometheus.
- **Cambios sin tiempo de inactividad.** Los upstreams, las ubicaciones, los plugins y los certificados se reemplazan en el proceso en ejecución (`--autoreload`). Lo que requiere nuevos listeners se aplica mediante un reinicio que transfiere los sockets (`--autorestart`, para un daemon en Linux), y el proceso en ejecución sigue sirviendo si su reemplazo no arranca.
- **Más de treinta plugins**, por ubicación y en el orden en que los enumeres: autenticación (JWT, OIDC, key, basic, HMAC, forward auth), reglas de acceso (IP, referer, user agent, rutas bloqueadas), tráfico (límites de tasa y ancho de banda, caché, mirroring, división de tráfico), contenido (compresión, sustitución de cuerpo, redirecciones, archivos estáticos, optimización de imágenes) y operaciones (modo mantenimiento, endpoint de salud, páginas de error). [Todos los plugins](https://github.com/vicanso/pingap/blob/main/pingap-plugin/README.md)
- **Certificados sin cron job.** Let's Encrypt o cualquier otra CA ACME, mediante HTTP-01 o DNS-01 (Alibaba Cloud, Cloudflare, Tencent Cloud, Huawei Cloud), RSA y ECDSA en paralelo, OCSP stapling, y TLS mutuo hacia los clientes y hacia los upstreams.
- **Configuración que se comprueba antes de ejecutarse.** `-t` construye todo lo que construiría un arranque, `--strict` rechaza las claves que no conoce, `--diff` muestra qué haría un cambio. TOML, HCL o KDL, en archivos o en etcd, con secretos tomados del entorno o de archivos.
- **Diseñado para ser observado.** Métricas de Prometheus (pull y push), trazas de OpenTelemetry, logs de acceso en texto o JSON con más de treinta campos, y huellas TLS JA4 de los clientes.
El resto de lo que hace un proxy inverso también está presente: HTTP/1.1, HTTP/2, WebSocket y gRPC-web; enrutamiento por host, ruta y expresión regular; round robin, least connections, consistent hashing y sesiones persistentes, con health checks, reintentos y un circuit breaker; backends desde DNS (A/AAAA, SRV) o etiquetas de Docker; el protocolo PROXY en los listeners y hacia los upstreams; OpenSSL o rustls.
Las métricas, el tracing y la optimización de imágenes están en las compilaciones `-full`.
## 🚀 Primeros pasos
### 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
```
El administrador está en http://localhost/pingap. Las imágenes están etiquetadas como `latest`, `full` y `rustls-full`, y `<version>`, `<version>-full`, `<version>-rustls-full` para una versión. Cada una también tiene una variante `-distroless` sin shell, donde `command` debe comenzar con `pingap` como se muestra arriba.
### Binario
```bash
curl -sSL https://raw.githubusercontent.com/vicanso/pingap/main/install.sh | sh
```
instala la última versión en `/usr/local/bin/pingap` en Linux y macOS (x86_64 y arm64). Las variables delante de `sh` eligen qué se instala:
| Variable | Efecto |
| --- | --- |
| `PINGAP_FULL=1` | La compilación `-full`, con todas las características opcionales |
| `PINGAP_TLS=rustls` | Linux: la compilación `-rustls-full`, sin OpenSSL (ver [TLS backend](#tls-backend)) |
| `PINGAP_LIBC=gnu` | Linux: la compilación glibc en lugar de la estática musl |
| `PINGAP_SERVICE=1` | Linux con systemd: también el servicio `pingap` y una configuración inicial en `/etc/pingap/conf`; no se habilita ni se inicia |
| `PINGAP_VERSION=0.15.0` | Esa versión en lugar de la última |
Todos los recursos están en la [página de releases](https://github.com/vicanso/pingap/releases).
### Un comando, sin archivo de configuración
```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
```
- Sin `--cert`, el certificado se solicita mediante HTTP-01: el dominio debe resolverse a este host, con el puerto 80 accesible. Se guarda en `~/.pingap/acme/<domains>.toml` y se reutiliza en el siguiente arranque. No lo elimine, la emisión está limitada por tasa.
- `--cert` es el certificado o el directorio en el que se encuentra (se buscan `fullchain.pem` / `privkey.pem`, `cert.pem` / `key.pem` y `tls.crt` / `tls.key`; `--key` nombra cualquier otro). `--upstream` y `--domain` aceptan listas separadas por comas, `--addr` la dirección en la que escuchar.
- La configuración se crea de nuevo en cada arranque, por lo que el administrador no puede editarla. Para más de un servidor use `--conf`, que no se puede combinar con estas flags.
## 📝 Configuración
```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"]
```
Lo mismo puede escribirse en HCL o KDL. Cada clave se describe en la [referencia de configuración](https://pingap.io/crates/config).
| | |
| --- | --- |
| `--autoreload` | Un cambio se aplica sin reiniciar: en menos de diez segundos desde archivos, de inmediato desde etcd. Lo que requiere un reinicio se informa y espera a uno. El modo para contenedores |
| `-a`, `--autorestart` | Lo mismo, y un reinicio que no descarta ninguna solicitud para lo que una recarga no puede cambiar, como una dirección de escucha. Para un daemon (`-d`) en Linux; en cualquier otro lugar recarga e informa lo que queda en espera |
| `-t` | Construye cada upstream, location, plugin, certificado y servidor como lo hace un arranque, y no enlaza nada |
| `--strict` | Una clave que Pingap no conoce, o una para la que esta compilación no tiene funcionalidad, es un error, donde de otro modo sería una advertencia. Para `-t` en CI |
| `--diff <other>` | Muestra qué cambiaría si `<other>` ocupara el lugar de la configuración, con las credenciales mostradas como sumas de verificación |
| `$ENV:NAME`, `$FILE:/path` | Como valor completo: se reemplaza por la variable o el archivo cuando se carga la configuración |
## 🔄 Cómo se gestiona una solicitud

1. El **servidor** acepta la conexión y elige la **location** cuyo host y ruta coinciden mejor. Una solicitud que no coincide con ninguna se responde con `404`.
2. Los **plugins** de la location se ejecutan en el orden en que aparecen, cada uno en su propio paso: a la ida o a la vuelta. Un plugin puede responder la solicitud por sí mismo —un acierto de caché, una autenticación fallida, una redirección, un archivo estático— y entonces nunca se consulta al upstream.
3. De lo contrario, el **upstream** de la location elige un backend saludable y la solicitud se reenvía. Con `max_retries`, una conexión que no se puede establecer se intenta en otro backend; con `fallback_upstream`, una solicitud que el upstream no puede aceptar pasa a un segundo upstream.
4. La respuesta vuelve a través de los plugins de respuesta hacia el cliente, y la solicitud se **registra**: log de acceso, métricas y trazas.
## 📊 Rendimiento
Unos 147.000 solicitudes por segundo para `/ping` en un hilo de un M4 Pro, con un percentil 99 de 116µs (`wrk`, 10 conexiones, sin log de acceso).
<details>
<summary>La ejecución</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>
## 🔧 Desarrollo
```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 con las características `full`, ejecuta Pingap con la configuración en `~/tmp/pingap` con `--autoreload`, sirve el admin en `127.0.0.1:3018` (`pingap` / `123123`) y recompila cuando cambian las fuentes. El admin se incrusta en el binario desde `dist/`, que `make build-web` rellena. La versión mínima de Rust soportada es 1.96.
### Backend TLS
La compilación por defecto usa OpenSSL, compilado desde el código fuente. Para rustls:
```bash
cargo build --release --no-default-features --features tls-rustls,full
```
Una compilación de rustls siempre ofrece TLS 1.2 y 1.3 con los propios conjuntos de cifrado de rustls, por lo que `tls_min_version`, `tls_max_version`, `tls_cipher_list` y `tls_ciphersuites` por servidor se rechazan cuando se valida la configuración. Todo lo demás se comporta igual; las diferencias se enumeran con el [crate certificate](https://pingap.io/crates/certificate). `pingap --version` indica qué backend tiene un binario.
## 🤝 Contribuir
Las pull requests son bienvenidas.
- Para una nueva funcionalidad, abre primero un issue, para que pueda discutirse antes de escribir código.
- No abras una pull request solo para corregir una errata o el formato de un comentario; estos se corrigen por lotes.
- Ejecuta `make fmt`, `make lint` y `make test` antes de hacer push. CI ejecuta las mismas comprobaciones en cada pull request.
- Una contribución se realiza bajo el [Contributor License Agreement](https://github.com/vicanso/pingap/blob/main/CLA.md), que se acepta marcando su casilla en la plantilla de pull request: el trabajo sigue siendo tuyo y se licencia al proyecto bajo su licencia.
## 📄 Licencia
[Apache License, Version 2.0](https://github.com/vicanso/pingap/blob/main/LICENSE).