Go
Только стандартная библиотека. Контекст в каждом методе, ошибки — через errors.Is, клиент безопасен для горутин.
| Модуль | Версии Go | Зависимости |
|---|---|---|
github.com/Homelander-77/PDF-creator-site/sdk/go | 1.21+ | нет |
Установка#
go get github.com/Homelander-77/PDF-creator-site/sdk/goКлюч берётся из переменной окружения PDFAPI_KEY — выпустите его в кабинете:
export PDFAPI_KEY=pdf_live_...Первый PDF#
package main
import (
"context"
"fmt"
"log"
pdfapi "github.com/Homelander-77/PDF-creator-site/sdk/go"
)
func main() {
ctx := context.Background()
client, err := pdfapi.NewClient() // ключ из PDFAPI_KEY
if err != nil {
log.Fatal(err)
}
pdf, err := client.HTML(ctx, "<h1>Счёт №42</h1>", &pdfapi.PageOptions{MarginTop: "20mm"})
if err != nil {
log.Fatal(err)
}
if err := pdf.Save("invoice.pdf"); err != nil {
log.Fatal(err)
}
fmt.Println(*pdf.Pages, "стр.") // сколько страниц списано
}Создайте один клиент и переиспользуйте его во всех горутинах.
Методы#
Первый аргумент у всех — context.Context: отмена и дедлайны работают как обычно.
| Метод | Что делает |
|---|---|
HTML(ctx, html, opts) | HTML → PDF |
URL(ctx, url, opts) | публичная страница по адресу → PDF. Тариф Premium и выше; адрес — полный, с https:// |
Markdown(ctx, text, opts) | Markdown → PDF. Тариф Premium и выше |
Office(ctx, filename, data, landscape) | docx, xlsx, pptx из памяти → PDF. Тариф Premium и выше |
OfficeFile(ctx, path, landscape) | то же с диска |
Convert(ctx, body) | POST /v1/convert с телом как есть |
Me(ctx) | тариф, расход и остаток страниц — GET /v1/me |
Параметры страницы#
*PageOptions, все поля необязательные; nil — значения по умолчанию. Что они значат — в разделе Параметры страницы.
| Поле | Пример | По умолчанию |
|---|---|---|
Landscape | pdfapi.Bool(true) | false |
PaperWidth, PaperHeight | "8.27in", "297mm" | A4 |
MarginTop, MarginBottom, MarginLeft, MarginRight | "20mm" | "0.39in" |
WaitDelay | "2s", "500ms", до 10 секунд | "0s" |
pdf, err := client.URL(ctx, "https://example.com/report", &pdfapi.PageOptions{
Landscape: pdfapi.Bool(true),
WaitDelay: "1s",
})Размер — число и единица pt, px, in, mm, cm или pc без пробела. "20" и "20 mm" вернут ErrInvalidArgument сразу, без запроса к API.
Документы Office#
pdf, err := client.OfficeFile(ctx, "report.docx", nil) // с диска
pdf, err = client.Office(ctx, "table.xlsx", data, pdfapi.Bool(true)) // из памяти — имя с расширениемФайл — до 3,7 МБ: в base64 он вырастает на треть, а тело запроса ограничено 5 МБ. Библиотека проверяет, что файл — настоящий ZIP-архив, поэтому старый .doc или PDF, переименованный в .docx, отклоняется до отправки.
Результат#
*Result:
Content []byte— байты PDF;Pages *int— сколько страниц списано;Quota *Quota—Limit,UsedиRemainingпосле этой конвертации;Save(path)— записать в файл;WriteTo(w)— записать вio.Writer, например вhttp.ResponseWriter.
Если сервер не прислал заголовки квоты, указатели — nil. Save пишет атомарно: сначала во временный файл рядом, потом переименовывает поверх. Оборвётся запись — останется прежний файл, а не обрезанный PDF.
Аккаунт и квота#
me, err := client.Me(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Println(me.Plan, me.PagesRemaining, "из", me.PagesLimit, "— обнулится", me.PeriodEnd)*Account: UserID, Plan, PeriodStart, PeriodEnd (UTC; нулевое время — сервер не сообщил), PagesUsed, PagesLimit, PagesRemaining, RequestsPerSecond, Burst. Запрос страниц не тратит. Как встроить проверку в cron — в руководстве Контроль квоты.
Ошибки#
Ошибки API — *pdfapi.Error с полями Code, Status и Message на русском языке. Вид ошибки проверяется через errors.Is:
pdf, err := client.HTML(ctx, html, nil)
if err != nil {
var apiErr *pdfapi.Error
switch {
case errors.Is(err, pdfapi.ErrQuotaExceeded):
// страницы кончились
case errors.Is(err, pdfapi.ErrRateLimit) && errors.As(err, &apiErr):
time.Sleep(apiErr.RetryAfter)
case errors.Is(err, pdfapi.ErrRender): // и ErrRenderTimeout тоже
// разметка не отрендерилась
case errors.As(err, &apiErr):
log.Println(apiErr.Code, apiErr.Status, apiErr.Message)
default:
log.Println(err) // например, context.Canceled — отменили вы сами
}
return
}| Значение | Когда |
|---|---|
ErrAuthentication | 401: ключа нет, он неверный или отозван |
ErrPermissionDenied | 403: почта не подтверждена или формат недоступен на тарифе |
ErrQuotaExceeded | 402: страницы периода кончились или документ больше остатка |
ErrInvalidRequest | 400: неверный параметр или адрес во внутреннюю сеть (Code == "url_not_allowed") |
ErrPayloadTooLarge | 413: тело больше 5 МБ; это и ErrInvalidRequest |
ErrRateLimit | 429: слишком часто; RetryAfter — через сколько можно снова |
ErrRender | 502: документ не отрендерился |
ErrRenderTimeout | 504: рендер дольше 30 секунд; это и ErrRender |
ErrUnavailable | 503: рендер временно недоступен |
ErrConnection | нет связи с API |
ErrTimeout | API не ответил за таймаут; это и ErrConnection |
ErrInvalidArgument | неверный аргумент — до запроса, лимит частоты не тратится |
Все коды API — в разделе Ошибки и повторы.
Повторы#
Библиотека сама повторяет запрос, когда это безопасно, — по умолчанию до двух раз (WithMaxRetries):
- 429 — через
Retry-After, но не дольше минуты; - 503 или нет соединения — первый повтор примерно через 0,5 секунды, второй — примерно через 1 секунду.
После таймаута результат может быть уже сохранён. Повтор с тем же Idempotency-Key и телом запроса защищает от повторного списания на обновлённом сервере. Без этого ключа повтор считается новой конвертацией. Подробнее.
Таймауты и отмена#
- Таймаут клиента —
WithTimeout, по умолчанию 40 секунд на одну попытку. - Для одного вызова —
context.WithTimeout: действует меньший из двух. - Отменили контекст сами — вернётся
ctx.Err(), а не ошибка API.
Настройки клиента#
client, err := pdfapi.NewClient(
pdfapi.WithAPIKey("pdf_live_..."), // иначе PDFAPI_KEY
pdfapi.WithBaseURL("https://pdfapi.ru/api"), // иначе PDFAPI_BASE_URL
pdfapi.WithTimeout(40*time.Second),
pdfapi.WithMaxRetries(2),
pdfapi.WithHTTPClient(&http.Client{Transport: myProxyTransport}),
)
if err != nil {
log.Fatal(err) // нет ключа или неверные настройки
}Таймаут не больше нуля или отрицательное число повторов — ErrInvalidArgument из NewClient. Ключа нет ни в client.String(), ни в текстах ошибок.
PDF в веб-приложении#
PDF уже в памяти — отдавайте байты, на диск сохранять незачем:
http.HandleFunc("/invoice", func(w http.ResponseWriter, r *http.Request) {
// r.Context(): клиент ушёл — запрос к pdfapi тоже отменяется.
pdf, err := client.HTML(r.Context(), "<h1>Счёт №42</h1>", nil)
if err != nil {
http.Error(w, "PDF не получился", http.StatusBadGateway)
return
}
w.Header().Set("Content-Type", "application/pdf")
_, _ = pdf.WriteTo(w)
})Данные пользователя в HTML — только через html/template или html.EscapeString, иначе это HTML-инъекция в документ — см. Безопасность и данные.
Примеры#
В папке examples/ модуля; запуск — PDFAPI_KEY=pdf_live_... go run ./examples/<имя>.
| Пример | Что показывает |
|---|---|
invoice | счёт из html/template — данные экранируются сами |
from-url | страница по адресу, альбомная, с ожиданием скриптов |
office | docx, xlsx или pptx в PDF рядом с исходным файлом |
batch | пачка документов в горутинах, в пределах лимита частоты |
quota | остаток страниц для cron: код выхода 2 при нехватке |
server | отдача PDF из net/http: WriteTo, контекст запроса, ошибки API — в HTTP-ответы |