feat: add API Key authentication, Dockerfile, and docker-compose
- core/auth.py: API Key auth via header X-API-Key (env PDFTOOLKIT_API_KEY) - Modo dev: sem chave configurada → acesso livre - Modo produção: exige chave, retorna 401/403 - api.py: endpoints protegidos com Depends(_auth_required) - /health e / permanecem públicos - Dockerfile: multi-stage (base → api → web) - docker-compose.yml: api + web com healthcheck, volumes e rede - .env.example: template de variáveis de ambiente - README.md: seções de autenticação e Docker
This commit is contained in:
11
.env.example
Normal file
11
.env.example
Normal file
@@ -0,0 +1,11 @@
|
||||
# ── pdf-toolkit — Variáveis de Ambiente ─────────────────────────────
|
||||
# Copie este arquivo para .env e ajuste os valores.
|
||||
|
||||
# ── Autenticação da API ─────────────────────────────────────────────
|
||||
# Se vazio/omitido, a API roda sem autenticação (modo dev).
|
||||
# Em produção, defina uma chave forte com 32+ caracteres.
|
||||
PDFTOOLKIT_API_KEY=my-super-secret-api-key-change-me
|
||||
|
||||
# ── Portas (personalizável via docker-compose) ──────────────────────
|
||||
API_PORT=8555
|
||||
WEB_PORT=8550
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -10,4 +10,7 @@ tmp/
|
||||
*.egg-info/
|
||||
dist/
|
||||
build/
|
||||
.pytest_cache/
|
||||
.pytest_cache/
|
||||
*.pyc
|
||||
.tmp/
|
||||
__pycache__
|
||||
39
Dockerfile
Normal file
39
Dockerfile
Normal file
@@ -0,0 +1,39 @@
|
||||
# ── Estágio 1: base ─────────────────────────────────────────────────
|
||||
FROM python:3.12-slim AS base
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ENV PYTHONUNBUFFERED=1 \
|
||||
PYTHONDONTWRITEBYTECODE=1 \
|
||||
PIP_NO_CACHE_DIR=1
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
libgl1-mesa-glx libglib2.0-0 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# ── Estágio 2: API ──────────────────────────────────────────────────
|
||||
FROM base AS api
|
||||
|
||||
COPY . .
|
||||
|
||||
EXPOSE 8555
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
||||
CMD python -c "import http.client; conn=http.client.HTTPConnection('localhost:8555'); conn.request('GET','/health'); r=conn.getresponse(); exit(0 if r.status==200 else 1)"
|
||||
|
||||
CMD ["uvicorn", "api:app", "--host", "0.0.0.0", "--port", "8555"]
|
||||
|
||||
# ── Estágio 3: Streamlit Web ────────────────────────────────────────
|
||||
FROM base AS web
|
||||
|
||||
COPY . .
|
||||
|
||||
EXPOSE 8550
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
||||
CMD python -c "import http.client; conn=http.client.HTTPConnection('localhost:8550'); conn.request('GET','/'); r=conn.getresponse(); exit(0 if r.status==200 else 1)"
|
||||
|
||||
CMD ["streamlit", "run", "app.py", "--server.port", "8550", "--server.address", "0.0.0.0", "--server.headless", "true"]
|
||||
76
README.md
76
README.md
@@ -4,6 +4,8 @@ Transforme, otimize e gerencie arquivos PDF com facilidade.
|
||||
|
||||
- **Interface Web**: Streamlit com upload drag-and-drop e configuração visual
|
||||
- **API REST**: FastAPI com documentação OpenAPI automática em `/docs`
|
||||
- **Autenticação**: API Key via header `X-API-Key` (configurável via env var)
|
||||
- **Docker**: containers prontos para produção via docker-compose
|
||||
- **Operações**: compressão, PDF/A ↔ PDF comum, merge, split, rotação, extração de texto e imagens
|
||||
|
||||
---
|
||||
@@ -14,8 +16,9 @@ Transforme, otimize e gerencie arquivos PDF com facilidade.
|
||||
|
||||
- Python 3.12+
|
||||
- pip
|
||||
- Docker (opcional, para containers)
|
||||
|
||||
### Instalação
|
||||
### Instalação (sem Docker)
|
||||
|
||||
```bash
|
||||
git clone <url-do-repositorio> pdf-toolkit
|
||||
@@ -23,7 +26,7 @@ cd pdf-toolkit
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### Execução
|
||||
### Execução (sem Docker)
|
||||
|
||||
**Streamlit (interface web):**
|
||||
```bash
|
||||
@@ -109,6 +112,75 @@ pdf-toolkit/
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Autenticação da API
|
||||
|
||||
A API suporta autenticação via **API Key** no header `X-API-Key`.
|
||||
|
||||
### Configuração
|
||||
|
||||
Defina a variável de ambiente `PDFTOOLKIT_API_KEY`:
|
||||
|
||||
```bash
|
||||
export PDFTOOLKIT_API_KEY="sua-chave-secreta-aqui"
|
||||
```
|
||||
|
||||
Se a variável **não** estiver definida, a API roda sem autenticação (modo desenvolvimento).
|
||||
|
||||
### Uso com curl
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8555/compress \
|
||||
-H "X-API-Key: sua-chave-secreta-aqui" \
|
||||
-F "file=@documento.pdf" \
|
||||
-o comprimido.pdf
|
||||
```
|
||||
|
||||
### Endpoints públicos (sem auth)
|
||||
|
||||
- `GET /` — raiz informativa
|
||||
- `GET /health` — health check
|
||||
|
||||
---
|
||||
|
||||
## 🐳 Docker
|
||||
|
||||
### Com docker-compose (recomendado)
|
||||
|
||||
```bash
|
||||
# 1. Copie e configure o .env
|
||||
cp .env.example .env
|
||||
# Edite .env com sua API Key
|
||||
|
||||
# 2. Inicie os serviços
|
||||
docker compose up -d --build
|
||||
|
||||
# Acesse:
|
||||
# - Streamlit: http://localhost:8550
|
||||
# - API: http://localhost:8555
|
||||
# - Swagger: http://localhost:8555/docs
|
||||
```
|
||||
|
||||
### Build manual
|
||||
|
||||
```bash
|
||||
# API
|
||||
docker build --target api -t pdf-toolkit-api .
|
||||
docker run -p 8555:8555 --env-file .env pdf-toolkit-api
|
||||
|
||||
# Web
|
||||
docker build --target web -t pdf-toolkit-web .
|
||||
docker run -p 8550:8550 --env-file .env pdf-toolkit-web
|
||||
```
|
||||
|
||||
### Estrutura dos containers
|
||||
|
||||
- **api**: FastAPI + uvicorn, porta 8555
|
||||
- **web**: Streamlit, porta 8550, depende do serviço `api`
|
||||
- Volume compartilhado `uploads` para arquivos temporários
|
||||
- Healthcheck em ambos os containers
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testes
|
||||
|
||||
```bash
|
||||
|
||||
137
api.py
137
api.py
@@ -1,23 +1,28 @@
|
||||
"""
|
||||
pdf-toolkit API — FastAPI com documentação OpenAPI automática.
|
||||
pdf-toolkit API — FastAPI com documentação OpenAPI automática e autenticação.
|
||||
|
||||
Endpoints REST para todas as operações do toolkit.
|
||||
Documentação interativa disponível em /docs (Swagger) e /redoc.
|
||||
|
||||
Autenticação via header X-API-Key.
|
||||
Variável de ambiente: PDFTOOLKIT_API_KEY
|
||||
Se não definida, a API roda sem autenticação (modo dev).
|
||||
|
||||
Uso:
|
||||
uvicorn api:app --host 0.0.0.0 --port 8555 --reload
|
||||
"""
|
||||
|
||||
import io
|
||||
import zipfile
|
||||
from typing import List, Optional
|
||||
from typing import List
|
||||
|
||||
from fastapi import FastAPI, File, UploadFile, Form, HTTPException, Query
|
||||
from fastapi import FastAPI, File, UploadFile, Form, HTTPException, Depends
|
||||
from fastapi.responses import Response, StreamingResponse
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
|
||||
from config import API_TITLE, API_VERSION, API_DESCRIPTION, MAX_UPLOAD_SIZE_BYTES, QUALITY_PRESETS
|
||||
from core.engine import PDFEngine
|
||||
from core.auth import verify_api_key, is_auth_enabled
|
||||
|
||||
engine = PDFEngine()
|
||||
|
||||
@@ -38,6 +43,16 @@ app.add_middleware(
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
# ── Dependência de autenticação ─────────────────────────────────────
|
||||
# Endpoints públicos (health, root) usam auth opcional
|
||||
# Endpoints de operação usam auth obrigatório (se configurado)
|
||||
|
||||
|
||||
def _auth_required(api_key: str = Depends(verify_api_key)):
|
||||
"""Dependência que aplica autenticação nos endpoints operacionais."""
|
||||
return api_key
|
||||
|
||||
|
||||
# ── Helpers ─────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -69,23 +84,35 @@ def _zip_response(files: List[tuple], zip_name: str = "resultados.zip") -> Strea
|
||||
)
|
||||
|
||||
|
||||
# ── Endpoint Raiz ───────────────────────────────────────────────────
|
||||
# ── Endpoints Públicos (sem auth) ───────────────────────────────────
|
||||
|
||||
|
||||
@app.get("/")
|
||||
def root():
|
||||
auth_info = {"authentication": "enabled"} if is_auth_enabled() else {"authentication": "disabled (dev mode)"}
|
||||
return {
|
||||
"app": API_TITLE,
|
||||
"version": API_VERSION,
|
||||
"docs": "/docs",
|
||||
"redoc": "/redoc",
|
||||
**auth_info,
|
||||
}
|
||||
|
||||
|
||||
# ── Inspect ─────────────────────────────────────────────────────────
|
||||
@app.get("/health")
|
||||
def health():
|
||||
"""Health check — sem autenticação."""
|
||||
return {"status": "ok", "version": API_VERSION, "auth": is_auth_enabled()}
|
||||
|
||||
|
||||
@app.post("/inspect", summary="Inspeciona metadados de um PDF")
|
||||
# ── Endpoints Protegidos ────────────────────────────────────────────
|
||||
|
||||
|
||||
@app.post(
|
||||
"/inspect",
|
||||
summary="Inspeciona metadados de um PDF",
|
||||
dependencies=[Depends(_auth_required)],
|
||||
)
|
||||
def inspect(file: UploadFile = File(...)):
|
||||
"""
|
||||
Retorna metadados detalhados do PDF: número de páginas,
|
||||
@@ -98,10 +125,11 @@ def inspect(file: UploadFile = File(...)):
|
||||
return info
|
||||
|
||||
|
||||
# ── Compress ────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@app.post("/compress", summary="Comprime um PDF")
|
||||
@app.post(
|
||||
"/compress",
|
||||
summary="Comprime um PDF",
|
||||
dependencies=[Depends(_auth_required)],
|
||||
)
|
||||
def compress(
|
||||
file: UploadFile = File(...),
|
||||
preset: str = Form("ebook"),
|
||||
@@ -115,14 +143,15 @@ def compress(
|
||||
raise HTTPException(400, f"Preset inválido. Opções: {', '.join(QUALITY_PRESETS.keys())}")
|
||||
data = _read_file(file)
|
||||
result = engine.compress(data, preset=preset)
|
||||
out_name = (file.filename or "documento.pdf").replace(".pdf", f"_comprimido.pdf")
|
||||
out_name = (file.filename or "documento.pdf").replace(".pdf", "_comprimido.pdf")
|
||||
return _pdf_response(result, out_name)
|
||||
|
||||
|
||||
# ── PDF/A → PDF comum ──────────────────────────────────────────────
|
||||
|
||||
|
||||
@app.post("/pdfa-to-regular", summary="Converte PDF/A para PDF comum")
|
||||
@app.post(
|
||||
"/pdfa-to-regular",
|
||||
summary="Converte PDF/A para PDF comum",
|
||||
dependencies=[Depends(_auth_required)],
|
||||
)
|
||||
def pdfa_to_regular(file: UploadFile = File(...)):
|
||||
"""Remove restrições PDF/A gerando um PDF comum padrão."""
|
||||
data = _read_file(file)
|
||||
@@ -130,15 +159,16 @@ def pdfa_to_regular(file: UploadFile = File(...)):
|
||||
if info.get("is_pdfa"):
|
||||
result = engine.pdfa_to_regular(data)
|
||||
else:
|
||||
result = data # já é PDF comum
|
||||
result = data
|
||||
out_name = (file.filename or "documento.pdf").replace(".pdf", "_comum.pdf")
|
||||
return _pdf_response(result, out_name)
|
||||
|
||||
|
||||
# ── Compress PDF/A ─────────────────────────────────────────────────
|
||||
|
||||
|
||||
@app.post("/compress-pdfa", summary="Comprime mantendo PDF/A")
|
||||
@app.post(
|
||||
"/compress-pdfa",
|
||||
summary="Comprime mantendo PDF/A",
|
||||
dependencies=[Depends(_auth_required)],
|
||||
)
|
||||
def compress_pdfa(
|
||||
file: UploadFile = File(...),
|
||||
quality: int = Form(60, ge=10, le=100),
|
||||
@@ -150,10 +180,11 @@ def compress_pdfa(
|
||||
return _pdf_response(result, out_name)
|
||||
|
||||
|
||||
# ── Merge ───────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@app.post("/merge", summary="Junta múltiplos PDFs em um")
|
||||
@app.post(
|
||||
"/merge",
|
||||
summary="Junta múltiplos PDFs em um",
|
||||
dependencies=[Depends(_auth_required)],
|
||||
)
|
||||
async def merge(files: List[UploadFile] = File(...)):
|
||||
"""Mescla 2 ou mais PDFs em ordem, retornando um único PDF."""
|
||||
if len(files) < 2:
|
||||
@@ -166,10 +197,11 @@ async def merge(files: List[UploadFile] = File(...)):
|
||||
return _pdf_response(result, "merged.pdf")
|
||||
|
||||
|
||||
# ── Split ───────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@app.post("/split", summary="Extrai intervalos de páginas de um PDF")
|
||||
@app.post(
|
||||
"/split",
|
||||
summary="Extrai intervalos de páginas de um PDF",
|
||||
dependencies=[Depends(_auth_required)],
|
||||
)
|
||||
def split(
|
||||
file: UploadFile = File(...),
|
||||
ranges: str = Form(..., description='Ex: "1-3,5,7-9"'),
|
||||
@@ -204,17 +236,16 @@ def split(
|
||||
return _zip_response(results, "split.zip")
|
||||
|
||||
|
||||
# ── Extract pages ──────────────────────────────────────────────────
|
||||
|
||||
|
||||
@app.post("/extract-pages", summary="Extrai páginas específicas")
|
||||
@app.post(
|
||||
"/extract-pages",
|
||||
summary="Extrai páginas específicas",
|
||||
dependencies=[Depends(_auth_required)],
|
||||
)
|
||||
def extract_pages(
|
||||
file: UploadFile = File(...),
|
||||
pages: str = Form(..., description='Ex: "1,3,5" (1-indexado)'),
|
||||
):
|
||||
"""
|
||||
Extrai páginas específicas de um PDF em ordem.
|
||||
"""
|
||||
"""Extrai páginas específicas de um PDF em ordem."""
|
||||
data = _read_file(file)
|
||||
reader = __import__("pypdf", fromlist=["PdfReader"]).PdfReader(io.BytesIO(data))
|
||||
total = len(reader.pages)
|
||||
@@ -233,10 +264,11 @@ def extract_pages(
|
||||
return _pdf_response(result, out_name)
|
||||
|
||||
|
||||
# ── Rotate ──────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@app.post("/rotate", summary="Rotaciona páginas de um PDF")
|
||||
@app.post(
|
||||
"/rotate",
|
||||
summary="Rotaciona páginas de um PDF",
|
||||
dependencies=[Depends(_auth_required)],
|
||||
)
|
||||
def rotate(
|
||||
file: UploadFile = File(...),
|
||||
pages: str = Form(..., description='Páginas: "1,3,5" ou "todas"'),
|
||||
@@ -259,10 +291,11 @@ def rotate(
|
||||
return _pdf_response(result, out_name)
|
||||
|
||||
|
||||
# ── Extract text ────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@app.post("/extract-text", summary="Extrai texto de um PDF")
|
||||
@app.post(
|
||||
"/extract-text",
|
||||
summary="Extrai texto de um PDF",
|
||||
dependencies=[Depends(_auth_required)],
|
||||
)
|
||||
def extract_text_endpoint(file: UploadFile = File(...)):
|
||||
"""Extrai todo o texto legível de um PDF e retorna como .txt."""
|
||||
data = _read_file(file)
|
||||
@@ -276,10 +309,11 @@ def extract_text_endpoint(file: UploadFile = File(...)):
|
||||
)
|
||||
|
||||
|
||||
# ── Extract images ──────────────────────────────────────────────────
|
||||
|
||||
|
||||
@app.post("/extract-images", summary="Extrai imagens de um PDF como ZIP")
|
||||
@app.post(
|
||||
"/extract-images",
|
||||
summary="Extrai imagens de um PDF como ZIP",
|
||||
dependencies=[Depends(_auth_required)],
|
||||
)
|
||||
def extract_images_endpoint(file: UploadFile = File(...)):
|
||||
"""Extrai todas as imagens embutidas no PDF e retorna um ZIP."""
|
||||
data = _read_file(file)
|
||||
@@ -289,15 +323,6 @@ def extract_images_endpoint(file: UploadFile = File(...)):
|
||||
return _zip_response(images, "imagens.zip")
|
||||
|
||||
|
||||
# ── Health check ────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
def health():
|
||||
"""Health check simples."""
|
||||
return {"status": "ok", "version": API_VERSION}
|
||||
|
||||
|
||||
# ── Entrypoint ──────────────────────────────────────────────────────
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
59
core/auth.py
Normal file
59
core/auth.py
Normal file
@@ -0,0 +1,59 @@
|
||||
"""
|
||||
Autenticação via API Key para a API REST.
|
||||
|
||||
A chave é lida da variável de ambiente PDFTOOLKIT_API_KEY.
|
||||
Se a variável não estiver definida, a autenticação fica desabilitada
|
||||
(útil para desenvolvimento local).
|
||||
|
||||
Uso:
|
||||
export PDFTOOLKIT_API_KEY="sua-chave-secreta-aqui"
|
||||
"""
|
||||
|
||||
import os
|
||||
from fastapi import Security, HTTPException, status
|
||||
from fastapi.security import APIKeyHeader
|
||||
|
||||
API_KEY_ENV = "PDFTOOLKIT_API_KEY"
|
||||
|
||||
# Lê a chave do ambiente (None se não configurada)
|
||||
_API_KEY = os.environ.get(API_KEY_ENV, None)
|
||||
|
||||
# Schema de header: X-API-Key
|
||||
api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False)
|
||||
|
||||
|
||||
def verify_api_key(api_key: str = Security(api_key_header)) -> str:
|
||||
"""
|
||||
Dependência FastAPI que valida a API Key.
|
||||
|
||||
- Se PDFTOOLKIT_API_KEY não foi definida no ambiente → permite acesso livre
|
||||
(modo desenvolvimento).
|
||||
- Se foi definida → exige que o header X-API-Key corresponda exatamente.
|
||||
|
||||
Retorna a própria chave se válida, ou levanta 401.
|
||||
"""
|
||||
# Modo desenvolvimento: sem chave configurada
|
||||
if _API_KEY is None:
|
||||
return "dev-mode"
|
||||
|
||||
# Modo produção: exige chave
|
||||
if api_key is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="API Key ausente. Envie o header X-API-Key.",
|
||||
headers={"WWW-Authenticate": "ApiKey"},
|
||||
)
|
||||
|
||||
if api_key != _API_KEY:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="API Key inválida.",
|
||||
headers={"WWW-Authenticate": "ApiKey"},
|
||||
)
|
||||
|
||||
return api_key
|
||||
|
||||
|
||||
def is_auth_enabled() -> bool:
|
||||
"""Retorna True se a autenticação estiver ativa."""
|
||||
return _API_KEY is not None
|
||||
58
docker-compose.yml
Normal file
58
docker-compose.yml
Normal file
@@ -0,0 +1,58 @@
|
||||
services:
|
||||
|
||||
# ── API REST (FastAPI) ──────────────────────────────────────────
|
||||
api:
|
||||
build:
|
||||
context: .
|
||||
target: api
|
||||
container_name: pdf-toolkit-api
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${API_PORT:-8555}:8555"
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
- PDFTOOLKIT_API_KEY=${PDFTOOLKIT_API_KEY:-}
|
||||
volumes:
|
||||
- uploads:/tmp/uploads
|
||||
healthcheck:
|
||||
test: ["CMD", "python", "-c", "import http.client; conn=http.client.HTTPConnection('localhost:8555'); conn.request('GET','/health'); r=conn.getresponse(); exit(0 if r.status==200 else 1)"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
networks:
|
||||
- pdf-net
|
||||
|
||||
# ── Interface Web (Streamlit) ───────────────────────────────────
|
||||
web:
|
||||
build:
|
||||
context: .
|
||||
target: web
|
||||
container_name: pdf-toolkit-web
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${WEB_PORT:-8550}:8550"
|
||||
depends_on:
|
||||
- api
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
- API_BASE_URL=http://api:8555
|
||||
volumes:
|
||||
- uploads:/tmp/uploads
|
||||
healthcheck:
|
||||
test: ["CMD", "python", "-c", "import http.client; conn=http.client.HTTPConnection('localhost:8550'); conn.request('GET','/'); r=conn.getresponse(); exit(0 if r.status==200 else 1)"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
networks:
|
||||
- pdf-net
|
||||
|
||||
volumes:
|
||||
uploads:
|
||||
|
||||
networks:
|
||||
pdf-net:
|
||||
driver: bridge
|
||||
25
healthz.py
Normal file
25
healthz.py
Normal file
@@ -0,0 +1,25 @@
|
||||
"""Adiciona endpoint /healthz para healthcheck do Docker."""
|
||||
import streamlit as st
|
||||
# O Streamlit não tem roteamento nativo, mas podemos expor via
|
||||
# arquivo estático. Para healthcheck do Docker, usamos o seguinte
|
||||
# truque: endpoint /healthz no mesmo host.
|
||||
# Como Streamlit não permite criar rotas customizadas, o healthcheck
|
||||
# será feito via HTTP na própria API (mais confiável).
|
||||
# Esta função é apenas um placeholder para documentação.
|
||||
"""
|
||||
Healthcheck do container web (Streamlit):
|
||||
|
||||
O Streamlit não expõe endpoints HTTP customizados nativamente.
|
||||
Para o healthcheck do Docker, usamos:
|
||||
test: ["CMD", "python", "-c", "import http.client; conn=http.client.HTTPConnection('localhost:8550'); conn.request('GET','/'); r=conn.getresponse(); exit(0 if r.status==200 else 1)"]
|
||||
|
||||
Isso verifica se o servidor Streamlit está respondendo na porta 8550.
|
||||
"""
|
||||
|
||||
# Adiciona uma página de health no sidebar para debug
|
||||
def show_health_page():
|
||||
st.set_page_config(page_title="Health Check", page_icon="💚")
|
||||
st.write("✅ pdf-toolkit web is running")
|
||||
|
||||
if __name__ == "__main__":
|
||||
show_health_page()
|
||||
Reference in New Issue
Block a user