Sluzba prijme adresu HTML dokumentu nebo HTML v tele requestu a vrati PDF. Navrzena pro dokumenty o stovkach az tisicich stranek. Rendering: - WeasyPrint jako vychozi engine, spravne CSS Paged Media, nizka pametova narocnost, bez JavaScriptu - Chromium pres Playwright pro dokumenty dokreslovane skripty - rezim auto s detekci skriptu a fallbackem pri selhani WeasyPrintu Velke dokumenty: - deleni na casti na strukturalnich hranicich, rez nikdy uvnitr tabulky nebo odstavce - dvoupruchodovy render obsahu se skutecnymi cisly stranek, pozice nadpisu se ctou z kotev hlasenych u kazde stranky - cislovani stranek bud pres CSS countery, nebo pres cislovaci vrstvu nastampovanou na hotove PDF, rozmer stranky se cte z vysledneho souboru - Chromium se restartuje po N jobech, nikdy vsak behem beziciho renderu API: - POST /convert synchronne, POST /jobs asynchronne se sledovanim stavu, stahovanim vysledku, rusenim a volitelnym callbackem - GET /health s overenim dostupnosti obou enginu a stavem fronty - OpenAPI respektuje prefix reverse proxy pres root_path Bezpecnost a provoz: - SSRF kontrola po DNS resolvu, na kazdem presmerovani a u vsech pozadavku prohlizece - nedostupne assety render nezastavi, ale hlasi se v odpovedi i v logu - fronta s omezenym poctem workeru, rozpracovane joby se pri ukonceni oznaci jako failed, nezmizi potichu - strukturovane JSON logovani s job_id - vsechny limity vypnute ve vychozim stavu Dockerfile je dvoufazovy, obsahuje zavislosti WeasyPrintu, Chromium a fonty s ceskou diakritikou. Autentizace zamerne neni implementovana, zpusob predavani neni domluveny. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
246 lines
7.6 KiB
Markdown
246 lines
7.6 KiB
Markdown
# API
|
|
|
|
Vsechny cesty jsou uvedene relativne. Verejne se volaji s prefixem
|
|
`/apps/html-to-pdf`, ktery Caddy pred predanim do containeru odstranuje.
|
|
|
|
Odpovedi jsou JSON, vyjimkou je stazeni hotoveho PDF.
|
|
|
|
## POST /convert
|
|
|
|
Synchronni prevod. Vraci primo `application/pdf`.
|
|
|
|
Urceno pro mensi dokumenty. Pokud konverze presahne `SYNC_TIMEOUT_SECONDS`
|
|
(vychozi 60 s), job se zrusi a sluzba vrati HTTP 413 s odkazem na asynchronni
|
|
endpoint. Zruseni se loguje, nikdy nezmizi potichu.
|
|
|
|
Hlavicky odpovedi:
|
|
|
|
| Hlavicka | Vyznam |
|
|
|---|---|
|
|
| `X-Page-Count` | pocet stranek vysledku |
|
|
| `X-Engine-Used` | engine, ktery dokument vyrenderoval |
|
|
| `X-Missing-Assets` | pocet assetu, ktere se nepodarilo nacist |
|
|
| `X-Warnings` | pocet varovani |
|
|
|
|
Pokud je `X-Missing-Assets` nenulovy, v PDF neco chybi. Detaily jsou dostupne
|
|
jen u asynchronni cesty, kde se vraci cely seznam.
|
|
|
|
## POST /jobs
|
|
|
|
Zaradi konverzi do fronty. Vraci HTTP 202.
|
|
|
|
```json
|
|
{
|
|
"job_id": "1f0e...",
|
|
"status": "queued",
|
|
"created_at": "2026-08-27T10:00:00Z",
|
|
"result_url": "/apps/html-to-pdf/jobs/1f0e.../result"
|
|
}
|
|
```
|
|
|
|
## GET /jobs/{job_id}
|
|
|
|
Stav jobu.
|
|
|
|
```json
|
|
{
|
|
"job_id": "1f0e...",
|
|
"status": "running",
|
|
"created_at": "2026-08-27T10:00:00Z",
|
|
"started_at": "2026-08-27T10:00:01Z",
|
|
"finished_at": null,
|
|
"expires_at": null,
|
|
"progress": {
|
|
"pages_rendered": 420,
|
|
"chunks_done": 9,
|
|
"chunks_total": 21,
|
|
"pass_number": 1
|
|
},
|
|
"engine_used": null,
|
|
"page_count": null,
|
|
"missing_assets": [],
|
|
"warnings": [],
|
|
"error": null,
|
|
"result_url": null
|
|
}
|
|
```
|
|
|
|
Stavy: `queued`, `running`, `done`, `failed`, `cancelled`, `expired`.
|
|
|
|
Pole `pass_number` rozlisuje prvni a druhy pruchod. Druhy pruchod nastava jen
|
|
tehdy, kdyz se generuje obsah se skutecnymi cisly stranek, takze u takoveho
|
|
dokumentu ukazatel postupu probehne dvakrat.
|
|
|
|
## GET /jobs/{job_id}/result
|
|
|
|
Stahne hotove PDF. Soubor se streamuje, nenacita se cely do pameti.
|
|
|
|
- 404 job neexistuje nebo uz expiroval
|
|
- 409 job jeste nedobehl nebo skoncil chybou
|
|
|
|
## DELETE /jobs/{job_id}
|
|
|
|
Zrusi bezici job nebo smaze hotovy vysledek.
|
|
|
|
## GET /health
|
|
|
|
Stav sluzby, verze, dostupnost enginu a stav fronty. U Chromia se dostupnost
|
|
overuje skutecnym nastartovanim prohlizece.
|
|
|
|
`status` je `ok`, pokud je k dispozici alespon jeden engine, jinak `degraded`.
|
|
|
|
## GET /version
|
|
|
|
Nazev aplikace, verze a aktualni `root_path`.
|
|
|
|
## GET /docs
|
|
|
|
Swagger UI. OpenAPI dokument obsahuje `servers` s prefixem `/apps/html-to-pdf`,
|
|
takze tlacitko Try it out vola spravnou verejnou cestu.
|
|
|
|
## Telo requestu
|
|
|
|
Stejne pro `/convert` i `/jobs`. Povinne je pouze `source`, vsechno ostatni ma
|
|
pouzitelnou vychozi hodnotu.
|
|
|
|
```json
|
|
{
|
|
"source": {
|
|
"url": "https://example.com/dokument.html",
|
|
"html": null,
|
|
"base_url": null
|
|
},
|
|
"engine": "auto",
|
|
"page": {
|
|
"format": "A4",
|
|
"orientation": "portrait",
|
|
"margin": { "top": "20mm", "right": "15mm", "bottom": "20mm", "left": "15mm" }
|
|
},
|
|
"page_numbers": {
|
|
"enabled": false,
|
|
"format": "{page} / {pages}",
|
|
"position": "bottom-center",
|
|
"mode": "auto",
|
|
"start_at": 1
|
|
},
|
|
"toc": { "enabled": false, "depth": 3, "title": "Obsah" },
|
|
"outline": true,
|
|
"pdf_profile": null,
|
|
"assets": { "allow_remote": true, "timeout_seconds": 10 },
|
|
"chunking": { "enabled": true, "pages_per_chunk": 50 },
|
|
"wait_for": { "state": "load", "selector": null, "timeout_seconds": 30 },
|
|
"filename": null,
|
|
"callback_url": null
|
|
}
|
|
```
|
|
|
|
### source
|
|
|
|
Vyplnene musi byt prave jedno z poli `url` a `html`, jinak sluzba vraci 422.
|
|
`base_url` slouzi k rozpadu relativnich cest a pouziva se hlavne spolu s `html`.
|
|
Pri pouziti `url` se `base_url` odvodi z finalni adresy po presmerovanich.
|
|
|
|
### engine
|
|
|
|
- `weasyprint` spravne strankovani, nizka pametova narocnost, nespousti JavaScript
|
|
- `chromium` zvladne i dokumenty dokreslovane JavaScriptem
|
|
- `auto` zvoli chromium, pokud dokument obsahuje aktivni skripty, jinak
|
|
weasyprint. Pokud render pres weasyprint selze, sluzba to zaloguje a zopakuje
|
|
ho pres chromium, coz se objevi ve `warnings`.
|
|
|
|
### page.format
|
|
|
|
Nazev formatu (`A4`, `A5`, `Letter`) nebo explicitni rozmer (`210mm 297mm`).
|
|
U nazvu se uplatni i `orientation`, u explicitniho rozmeru je orientace dana
|
|
poradim hodnot.
|
|
|
|
### page_numbers.mode
|
|
|
|
- `css` cislovani resi CSS countery pri renderu. Nejhezci vysledek, ale funguje
|
|
jen kdyz se cely dokument renderuje najednou, protoze v kazde casti se citac
|
|
stranek restartuje. Vyzaduje `engine: weasyprint` a `chunking.enabled: false`,
|
|
jinak sluzba vraci 400 s kodem `unsupported_combination`.
|
|
- `overlay` cisla se dopisi do hotoveho PDF jako pruhledna vrstva. Jedina
|
|
moznost u clenenych dokumentu a u Chromia.
|
|
- `auto` zvoli `css` u necleneneho dokumentu renderovaneho WeasyPrintem,
|
|
jinak `overlay`.
|
|
|
|
Cislovaci vrstvu kresli WeasyPrint i tehdy, kdyz dokument vyrenderovalo
|
|
Chromium. Bez nainstalovaneho WeasyPrintu proto cislovani stranek nefunguje.
|
|
|
|
### toc
|
|
|
|
Generuje obsah s odkazy a skutecnymi cisly stranek. Vyzaduje
|
|
`engine: weasyprint`, protoze Chromium neumi rict, na ktere strance nadpis
|
|
skoncil. Pri jine kombinaci sluzba vraci 400.
|
|
|
|
Nadpisy bez atributu `id` ho dostanou automaticky.
|
|
|
|
Pokud je dokument rozdelen na casti, odkazy v obsahu nejsou klikatelne, protoze
|
|
cil lezi v jine casti. Cisla stranek jsou spravna. Sluzba na to upozorni ve
|
|
`warnings`.
|
|
|
|
### outline
|
|
|
|
Zalozky PDF generovane z nadpisu h1 az h6. Vypnuti se resi CSS pravidlem
|
|
`bookmark-level: none`, takze funguje i uvnitr jednotlivych casti.
|
|
|
|
### pdf_profile
|
|
|
|
Predava se WeasyPrintu jako `pdf_variant`. Povolene hodnoty: `pdf/a-1b`,
|
|
`pdf/a-2b`, `pdf/a-3b`, `pdf/a-4b`, `pdf/ua-1`.
|
|
|
|
### assets
|
|
|
|
`allow_remote: false` zakaze stahovani externich assetu. Nedostupne assety
|
|
render nezastavi, ale objevi se v `missing_assets` a v logu.
|
|
|
|
### chunking
|
|
|
|
`pages_per_chunk` je cilova velikost casti. Skutecny pocet stranek je znamy az
|
|
po renderu, deleni proto vychazi z odhadu podle mnozstvi textu, obrazku a radku
|
|
tabulek. Rez vznika vzdy jen mezi primymi potomky kontejneru, takze tabulka ani
|
|
odstavec se nikdy nerozdeli.
|
|
|
|
Delici body jsou elementy `section`, `article`, `h1`, elementy s atributem
|
|
`data-chunk` a elementy se stylem obsahujicim `page-break-before` nebo
|
|
`break-before`. Pokud dokument zadny takovy nema, renderuje se vcelku a sluzba
|
|
to zaloguje.
|
|
|
|
### wait_for
|
|
|
|
Pouziva jen Chromium. `state` je `load`, `domcontentloaded` nebo `networkidle`,
|
|
`selector` navic ceka na konkretni element.
|
|
|
|
### callback_url
|
|
|
|
Po dokonceni nebo selhani jobu na nej sluzba posle POST se stejnym telem, jake
|
|
vraci `GET /jobs/{job_id}`. Neuspech se loguje a nekolikrat zopakuje, job se
|
|
kvuli nemu neoznaci jako failed.
|
|
|
|
## Chyby
|
|
|
|
```json
|
|
{
|
|
"error_code": "blocked_target",
|
|
"message": "Cilova adresa smeruje do privatniho nebo vyhrazeneho rozsahu a je zablokovana.",
|
|
"detail": { "url": "http://127.0.0.1/a.html", "host": "127.0.0.1" }
|
|
}
|
|
```
|
|
|
|
| error_code | HTTP | Kdy nastane |
|
|
|---|---|---|
|
|
| `invalid_request` | 400 | chybny vstup |
|
|
| `blocked_target` | 400 | adresa smeruje do zakazaneho rozsahu nebo ma nepovolene schema |
|
|
| `unsupported_combination` | 400 | nepodporovana kombinace parametru, napriklad obsah s Chromiem |
|
|
| `limit_exceeded` | 400 | prekrocen nakonfigurovany limit |
|
|
| `source_unavailable` | 502 | zdrojovy dokument se nepodarilo stahnout |
|
|
| `render_timeout` | 504 | render presahl casovy limit |
|
|
| `sync_too_long` | 413 | synchronni konverze presahla limit, pouzijte POST /jobs |
|
|
| `engine_unavailable` | 503 | pozadovany engine neni k dispozici |
|
|
| `queue_full` | 503 | fronta je plna |
|
|
| `job_not_found` | 404 | job neexistuje nebo expiroval |
|
|
| `result_not_ready` | 409 | job jeste nedobehl |
|
|
| `service_restarted` | v tele jobu | sluzba se restartovala drive, nez job dobehl |
|
|
| `internal_error` | 500 | neocekavana chyba |
|