mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-14 19:32:15 +01:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
36b4d435c3 | ||
|
|
a8bde9d396 | ||
|
|
5c6f29095f | ||
|
|
0d3942848c | ||
|
|
9256a203ef | ||
|
|
f13e045e7f | ||
|
|
a60cb33ff1 | ||
|
|
dec32d981f | ||
|
|
3046615c3c | ||
|
|
130b94aa8c |
@@ -3,8 +3,9 @@ linters-settings:
|
|||||||
sections:
|
sections:
|
||||||
- standard
|
- standard
|
||||||
- default
|
- default
|
||||||
- prefix(github.com/gotenberg/gotenberg/v8)
|
- prefix(github.com/gotenberg/gotenberg/v7)
|
||||||
skip-generated: true
|
skip-generated: true
|
||||||
|
skip-vendor: true
|
||||||
custom-order: true
|
custom-order: true
|
||||||
|
|
||||||
linters:
|
linters:
|
||||||
@@ -26,7 +27,7 @@ linters:
|
|||||||
- unused
|
- unused
|
||||||
|
|
||||||
run:
|
run:
|
||||||
timeout: 5m
|
deadline: 5m
|
||||||
issues-exit-code: 1
|
issues-exit-code: 1
|
||||||
tests: false
|
tests: false
|
||||||
|
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2024 Julien Neuhart
|
Copyright (c) 2023 Julien Neuhart
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
8
Makefile
8
Makefile
@@ -46,8 +46,6 @@ CHROMIUM_HOST_RESOLVER_RULES=
|
|||||||
CHROMIUM_PROXY_SERVER=
|
CHROMIUM_PROXY_SERVER=
|
||||||
CHROMIUM_ALLOW_LIST=
|
CHROMIUM_ALLOW_LIST=
|
||||||
CHROMIUM_DENY_LIST="^file:///[^tmp].*"
|
CHROMIUM_DENY_LIST="^file:///[^tmp].*"
|
||||||
CHROMIUM_CLEAR_CACHE=false
|
|
||||||
CHROMIUM_CLEAR_COOKIES=false
|
|
||||||
CHROMIUM_DISABLE_JAVASCRIPT=false
|
CHROMIUM_DISABLE_JAVASCRIPT=false
|
||||||
CHROMIUM_DISABLE_ROUTES=false
|
CHROMIUM_DISABLE_ROUTES=false
|
||||||
LIBREOFFICE_RESTART_AFTER=10
|
LIBREOFFICE_RESTART_AFTER=10
|
||||||
@@ -99,8 +97,6 @@ run: ## Start a Gotenberg container
|
|||||||
--chromium-proxy-server=$(CHROMIUM_PROXY_SERVER) \
|
--chromium-proxy-server=$(CHROMIUM_PROXY_SERVER) \
|
||||||
--chromium-allow-list=$(CHROMIUM_ALLOW_LIST) \
|
--chromium-allow-list=$(CHROMIUM_ALLOW_LIST) \
|
||||||
--chromium-deny-list=$(CHROMIUM_DENY_LIST) \
|
--chromium-deny-list=$(CHROMIUM_DENY_LIST) \
|
||||||
--chromium-clear-cache=$(CHROMIUM_CLEAR_CACHE) \
|
|
||||||
--chromium-clear-cookies=$(CHROMIUM_CLEAR_COOKIES) \
|
|
||||||
--chromium-disable-javascript=$(CHROMIUM_DISABLE_JAVASCRIPT) \
|
--chromium-disable-javascript=$(CHROMIUM_DISABLE_JAVASCRIPT) \
|
||||||
--chromium-disable-routes=$(CHROMIUM_DISABLE_ROUTES) \
|
--chromium-disable-routes=$(CHROMIUM_DISABLE_ROUTES) \
|
||||||
--libreoffice-restart-after=$(LIBREOFFICE_RESTART_AFTER) \
|
--libreoffice-restart-after=$(LIBREOFFICE_RESTART_AFTER) \
|
||||||
@@ -155,13 +151,13 @@ tests-once: ## Run the tests once (prefer the "tests" command while developing)
|
|||||||
.PHONY: fmt
|
.PHONY: fmt
|
||||||
fmt: ## Format the code and "optimize" the dependencies
|
fmt: ## Format the code and "optimize" the dependencies
|
||||||
gofumpt -l -w .
|
gofumpt -l -w .
|
||||||
gci write -s standard -s default -s "prefix(github.com/gotenberg/gotenberg/v8)" --skip-generated --skip-vendor --custom-order .
|
gci write -s standard -s default -s "prefix(github.com/gotenberg/gotenberg/v7)" --skip-generated --skip-vendor --custom-order .
|
||||||
go mod tidy
|
go mod tidy
|
||||||
|
|
||||||
# go install golang.org/x/tools/cmd/godoc@latest
|
# go install golang.org/x/tools/cmd/godoc@latest
|
||||||
.PHONY: godoc
|
.PHONY: godoc
|
||||||
godoc: ## Run a webserver with Gotenberg godoc
|
godoc: ## Run a webserver with Gotenberg godoc
|
||||||
$(info http://localhost:6060/pkg/github.com/gotenberg/gotenberg/v8)
|
$(info http://localhost:6060/pkg/github.com/gotenberg/gotenberg/v7)
|
||||||
godoc -http=:6060
|
godoc -http=:6060
|
||||||
|
|
||||||
.PHONY: release
|
.PHONY: release
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ numerous document formats (HTML, Markdown, Word, Excel, etc.) into PDF files, an
|
|||||||
Open a terminal and run the following command:
|
Open a terminal and run the following command:
|
||||||
|
|
||||||
```
|
```
|
||||||
docker run --rm -p 3000:3000 gotenberg/gotenberg:8
|
docker run --rm -p 3000:3000 gotenberg/gotenberg:7
|
||||||
```
|
```
|
||||||
|
|
||||||
Alternatively, using the historic Docker repository from our sponsor [TheCodingMachine](https://www.thecodingmachine.com):
|
Alternatively, using the historic Docker repository from our sponsor [TheCodingMachine](https://www.thecodingmachine.com):
|
||||||
|
|
||||||
```
|
```
|
||||||
docker run --rm -p 3000:3000 thecodingmachine/gotenberg:8
|
docker run --rm -p 3000:3000 thecodingmachine/gotenberg:7
|
||||||
```
|
```
|
||||||
|
|
||||||
The API is now available on your host at http://localhost:3000.
|
The API is now available on your host at http://localhost:3000.
|
||||||
@@ -41,5 +41,5 @@ Head to the [documentation](https://gotenberg.dev/docs/getting-started/introduct
|
|||||||
[](https://hub.docker.com/r/gotenberg/gotenberg)
|
[](https://hub.docker.com/r/gotenberg/gotenberg)
|
||||||
[](https://hub.docker.com/r/thecodingmachine/gotenberg)
|
[](https://hub.docker.com/r/thecodingmachine/gotenberg)
|
||||||
[](https://github.com/gotenberg/gotenberg/actions/workflows/continuous_integration.yml)
|
[](https://github.com/gotenberg/gotenberg/actions/workflows/continuous_integration.yml)
|
||||||
[](https://pkg.go.dev/github.com/gotenberg/gotenberg/v8)
|
[](https://pkg.go.dev/github.com/gotenberg/gotenberg/v7)
|
||||||
[](https://codecov.io/gh/gotenberg/gotenberg)
|
[](https://codecov.io/gh/gotenberg/gotenberg)
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ RUN go mod download &&\
|
|||||||
COPY cmd ./cmd
|
COPY cmd ./cmd
|
||||||
COPY pkg ./pkg
|
COPY pkg ./pkg
|
||||||
|
|
||||||
RUN go build -o gotenberg -ldflags "-X 'github.com/gotenberg/gotenberg/v8/cmd.Version=$GOTENBERG_VERSION'" cmd/gotenberg/main.go
|
RUN go build -o gotenberg -ldflags "-X 'github.com/gotenberg/gotenberg/v7/cmd.Version=$GOTENBERG_VERSION'" cmd/gotenberg/main.go
|
||||||
|
|
||||||
# ----------------------------------------------
|
# ----------------------------------------------
|
||||||
# Final stage
|
# Final stage
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
flag "github.com/spf13/pflag"
|
flag "github.com/spf13/pflag"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||||
)
|
)
|
||||||
|
|
||||||
// See https://patorjk.com/software/taag/#p=display&f=Small%20Slant&t=Gotenberg.
|
// See https://patorjk.com/software/taag/#p=display&f=Small%20Slant&t=Gotenberg.
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
gotenbergcmd "github.com/gotenberg/gotenberg/v8/cmd"
|
gotenbergcmd "github.com/gotenberg/gotenberg/v7/cmd"
|
||||||
// Gotenberg modules.
|
// Gotenberg modules.
|
||||||
_ "github.com/gotenberg/gotenberg/v8/pkg/standard"
|
_ "github.com/gotenberg/gotenberg/v7/pkg/standard"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|||||||
744
docs/openapi.yaml
Normal file
744
docs/openapi.yaml
Normal file
@@ -0,0 +1,744 @@
|
|||||||
|
openapi: 3.0.3
|
||||||
|
info:
|
||||||
|
title: Gotenberg
|
||||||
|
version: 7.x
|
||||||
|
license:
|
||||||
|
name: MIT
|
||||||
|
url: 'https://github.com/gotenberg/gotenberg/blob/main/LICENSE'
|
||||||
|
contact:
|
||||||
|
url: 'https://github.com/gotenberg/gotenberg'
|
||||||
|
description: >-
|
||||||
|
A Docker-powered stateless API for PDF files.
|
||||||
|
externalDocs:
|
||||||
|
url: https://gotenberg.dev
|
||||||
|
servers:
|
||||||
|
- url: 'http://localhost:3000'
|
||||||
|
description: Local server with the default Docker image and port
|
||||||
|
tags:
|
||||||
|
- name: chromium
|
||||||
|
description: Operations of the Chromium module
|
||||||
|
externalDocs:
|
||||||
|
url: https://gotenberg.dev/docs/modules/chromium
|
||||||
|
- name: libreoffice
|
||||||
|
description: Operations of the Libreoffice module
|
||||||
|
externalDocs:
|
||||||
|
url: https://gotenberg.dev/docs/modules/libreoffice
|
||||||
|
- name: pdfengines
|
||||||
|
description: Operations of the PDF Engines module
|
||||||
|
externalDocs:
|
||||||
|
url: https://gotenberg.dev/docs/modules/pdf-engines
|
||||||
|
paths:
|
||||||
|
/forms/chromium/convert/url:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- chromium
|
||||||
|
summary: Convert the contents of a given URL to PDF
|
||||||
|
externalDocs:
|
||||||
|
url: https://gotenberg.dev/docs/modules/chromium
|
||||||
|
description: >-
|
||||||
|
Send a URL in your API request via the `url` form field
|
||||||
|
Send a remote URL in your API request via the `remoteURL` parameter, and
|
||||||
|
get the resulting PDF file. The API will fetch the given URL and render
|
||||||
|
the page to PDF using the underlying headless Chrome instance.
|
||||||
|
You can optionally include `header.html` and `footer.html` files as part of the request as well.
|
||||||
|
See externalDocs for more details.
|
||||||
|
parameters:
|
||||||
|
- in: header
|
||||||
|
name: Gotenberg-Output-Filename
|
||||||
|
description: >-
|
||||||
|
By default, the API generates a UUID filename.
|
||||||
|
However, you may also specify the filename per request,
|
||||||
|
thanks to the Gotenberg-Output-Filename header.
|
||||||
|
Caution! The API adds the file extension automatically; you don't have to set it.
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
required: false
|
||||||
|
- in: header
|
||||||
|
name: Gotenberg-Trace
|
||||||
|
description: >-
|
||||||
|
The trace, or request ID, identifies a request in the logs.
|
||||||
|
|
||||||
|
By default, the API generates a UUID trace for each request.
|
||||||
|
However, you may also specify the trace per request, thanks to the Gotenberg-Trace header.
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
required: false
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
description: >-
|
||||||
|
The request must be `multipart/form-data` that includes a `url` form field.
|
||||||
|
The API uses a headless Chrome instance to do the conversion, therefore print
|
||||||
|
parameter such as margins and paper size are also accepted as optional parameters.
|
||||||
|
content:
|
||||||
|
multipart/form-data:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/URLConvertRequestBody'
|
||||||
|
examples: { }
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
$ref: '#/components/responses/SuccessfulPDF'
|
||||||
|
'400':
|
||||||
|
description: Bad Request
|
||||||
|
'503':
|
||||||
|
$ref: '#/components/responses/Timeout'
|
||||||
|
|
||||||
|
/forms/chromium/convert/html:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- chromium
|
||||||
|
summary: Convert a given HTML file to PDF
|
||||||
|
externalDocs:
|
||||||
|
url: https://gotenberg.dev/docs/modules/chromium
|
||||||
|
description: >-
|
||||||
|
Send an HTML file called `index.html` as a multipart form request, and
|
||||||
|
get the resulting PDF file. You can optionally include `header.html` and
|
||||||
|
`footer.html` files as part of the request as well.
|
||||||
|
See externalDocs for more details.
|
||||||
|
parameters:
|
||||||
|
- in: header
|
||||||
|
name: Gotenberg-Output-Filename
|
||||||
|
description: >-
|
||||||
|
By default, the API generates a UUID filename.
|
||||||
|
However, you may also specify the filename per request,
|
||||||
|
thanks to the Gotenberg-Output-Filename header.
|
||||||
|
Caution! The API adds the file extension automatically; you don't have to set it.
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
required: false
|
||||||
|
- in: header
|
||||||
|
name: Gotenberg-Trace
|
||||||
|
description: >-
|
||||||
|
The trace, or request ID, identifies a request in the logs.
|
||||||
|
|
||||||
|
By default, the API generates a UUID trace for each request.
|
||||||
|
However, you may also specify the trace per request, thanks to the Gotenberg-Trace header.
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
required: false
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
multipart/form-data:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/HTMLConvertRequestBody'
|
||||||
|
description: >-
|
||||||
|
The request body must have an `index.html` file in the `files` array,
|
||||||
|
as well as all the referred resources on the same level as the
|
||||||
|
`index.html` file. The request can also include `header.html` and
|
||||||
|
`footer.html`, given the limitations in the API description above.
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
$ref: '#/components/responses/SuccessfulPDF'
|
||||||
|
'400':
|
||||||
|
description: Bad Request
|
||||||
|
'503':
|
||||||
|
$ref: '#/components/responses/Timeout'
|
||||||
|
|
||||||
|
/forms/chromium/convert/markdown:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- chromium
|
||||||
|
summary: Convert a Markdown file to PDF
|
||||||
|
externalDocs:
|
||||||
|
url: https://gotenberg.dev/docs/modules/chromium
|
||||||
|
description: >-
|
||||||
|
Accepts an HTML file called `index.html` plus markdown files as a multipart
|
||||||
|
form request and embeds the markdown files into the HTML file using the Golang template
|
||||||
|
function `toHTML`.
|
||||||
|
The API will convert the markdown to HTML and embed it into your `index.html` file,
|
||||||
|
then render the resulting page. You can include your own styling and more in your HTML file.
|
||||||
|
Refer to the HTML conversion page for all the options you can use when converting
|
||||||
|
Markdown documents as well. You can optionally include `header.html` and
|
||||||
|
`footer.html` files as part of the request as well.
|
||||||
|
See externalDocs for more details.
|
||||||
|
parameters:
|
||||||
|
- in: header
|
||||||
|
name: Gotenberg-Output-Filename
|
||||||
|
description: >-
|
||||||
|
By default, the API generates a UUID filename.
|
||||||
|
However, you may also specify the filename per request,
|
||||||
|
thanks to the Gotenberg-Output-Filename header.
|
||||||
|
Caution! The API adds the file extension automatically; you don't have to set it.
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
required: false
|
||||||
|
- in: header
|
||||||
|
name: Gotenberg-Trace
|
||||||
|
description: >-
|
||||||
|
The trace, or request ID, identifies a request in the logs.
|
||||||
|
|
||||||
|
By default, the API generates a UUID trace for each request.
|
||||||
|
However, you may also specify the trace per request, thanks to the Gotenberg-Trace header.
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
required: false
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
multipart/form-data:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/MarkdownConvertRequestBody'
|
||||||
|
description: >-
|
||||||
|
The request body must have an `index.html` file in the `files` array,
|
||||||
|
as well as all the referred markdown resources on the same level as
|
||||||
|
the `index.html` file.
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
$ref: '#/components/responses/SuccessfulPDF'
|
||||||
|
'400':
|
||||||
|
description: Bad Request
|
||||||
|
'503':
|
||||||
|
$ref: '#/components/responses/Timeout'
|
||||||
|
|
||||||
|
/forms/libreoffice/convert:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- libreoffice
|
||||||
|
summary: Convert an Office document to PDF
|
||||||
|
externalDocs:
|
||||||
|
url: https://gotenberg.dev/docs/modules/libreoffice
|
||||||
|
description: >-
|
||||||
|
This route accepts multipart/form-data requests and files with the following extensions:
|
||||||
|
|
||||||
|
.bib .doc .xml .docx .fodt .html .ltx .txt .odt .ott .pdb .pdf .psw .rtf
|
||||||
|
.sdw .stw .sxw .uot .vor .wps .epub .png .bmp .emf .eps .fodg .gif .jpg
|
||||||
|
.met .odd .otg .pbm .pct .pgm .ppm .ras .std .svg .svm .swf .sxd .sxw
|
||||||
|
.tiff .xhtml .xpm .fodp .potm .pot .pptx .pps .ppt .pwp .sda .sdd .sti
|
||||||
|
.sxi .uop .wmf .csv .dbf .dif .fods .ods .ots .pxl .sdc .slk .stc .sxc
|
||||||
|
.uos .xls .xlt .xlsx .tif .jpeg .odp
|
||||||
|
|
||||||
|
By default, if you send more than one file to convert, the route returns a ZIP archive of the
|
||||||
|
resulting PDF files. However, you may prefer to merge all the PDF files into an individual PDF file.
|
||||||
|
|
||||||
|
> **Attention:** The files will be merged alphabetically for the
|
||||||
|
resulting PDF.
|
||||||
|
|
||||||
|
You may also specify the page ranges to convert from the incoming Office
|
||||||
|
documents. The expected format is the same as the one from the print
|
||||||
|
options of LibreOffice, e.g. `1-1` or `1-4`.
|
||||||
|
|
||||||
|
> **Attention:** if more than one document, the page ranges will be
|
||||||
|
applied for each document.
|
||||||
|
|
||||||
|
See externalDocs for more details.
|
||||||
|
parameters:
|
||||||
|
- in: header
|
||||||
|
name: Gotenberg-Output-Filename
|
||||||
|
description: >-
|
||||||
|
By default, the API generates a UUID filename.
|
||||||
|
However, you may also specify the filename per request,
|
||||||
|
thanks to the Gotenberg-Output-Filename header.
|
||||||
|
Caution! The API adds the file extension automatically; you don't have to set it.
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
required: false
|
||||||
|
- in: header
|
||||||
|
name: Gotenberg-Trace
|
||||||
|
description: >-
|
||||||
|
The trace, or request ID, identifies a request in the logs.
|
||||||
|
|
||||||
|
By default, the API generates a UUID trace for each request.
|
||||||
|
However, you may also specify the trace per request, thanks to the Gotenberg-Trace header.
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
required: false
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
multipart/form-data:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/OfficeConvertRequestBody'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
$ref: '#/components/responses/SuccessfulPDF'
|
||||||
|
'400':
|
||||||
|
description: Bad Request, e.g. Both 'pdfFormat' and 'nativePdfA1aFormat' form values are provided
|
||||||
|
'503':
|
||||||
|
$ref: '#/components/responses/Timeout'
|
||||||
|
|
||||||
|
/forms/pdfengines/merge:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- pdfengines
|
||||||
|
summary: Merge multiple PDFs into a single PDF
|
||||||
|
externalDocs:
|
||||||
|
url: https://gotenberg.dev/docs/modules/pdf-engines
|
||||||
|
description: >-
|
||||||
|
You can send multiple PDF files to this endpoint, the API will merge
|
||||||
|
them into a single PDF and return the resulting PDF file.
|
||||||
|
|
||||||
|
> **Attention:** The PDF files will be merged alphabetically.
|
||||||
|
parameters:
|
||||||
|
- in: header
|
||||||
|
name: Gotenberg-Output-Filename
|
||||||
|
description: >-
|
||||||
|
By default, the API generates a UUID filename.
|
||||||
|
However, you may also specify the filename per request,
|
||||||
|
thanks to the Gotenberg-Output-Filename header.
|
||||||
|
Caution! The API adds the file extension automatically; you don't have to set it.
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
required: false
|
||||||
|
- in: header
|
||||||
|
name: Gotenberg-Trace
|
||||||
|
description: >-
|
||||||
|
The trace, or request ID, identifies a request in the logs.
|
||||||
|
|
||||||
|
By default, the API generates a UUID trace for each request.
|
||||||
|
However, you may also specify the trace per request, thanks to the Gotenberg-Trace header.
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
required: false
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
multipart/form-data:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
files:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
format: binary
|
||||||
|
pdfFormat:
|
||||||
|
type: string
|
||||||
|
description: The PDF format of the resulting PDF
|
||||||
|
example: PDF/A-1a
|
||||||
|
required:
|
||||||
|
- files
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
$ref: '#/components/responses/SuccessfulPDF'
|
||||||
|
'400':
|
||||||
|
description: Bad Request
|
||||||
|
'503':
|
||||||
|
$ref: '#/components/responses/Timeout'
|
||||||
|
|
||||||
|
/forms/pdfengines/convert:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- pdfengines
|
||||||
|
summary: Convert PDFs into the given formats
|
||||||
|
externalDocs:
|
||||||
|
url: https://gotenberg.dev/docs/modules/pdf-engines
|
||||||
|
description: >-
|
||||||
|
This route accepts PDF files and a form field pdfFormat for converting them into the specified format.
|
||||||
|
parameters:
|
||||||
|
- in: header
|
||||||
|
name: Gotenberg-Output-Filename
|
||||||
|
description: >-
|
||||||
|
By default, the API generates a UUID filename.
|
||||||
|
However, you may also specify the filename per request,
|
||||||
|
thanks to the Gotenberg-Output-Filename header.
|
||||||
|
Caution! The API adds the file extension automatically; you don't have to set it.
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
required: false
|
||||||
|
- in: header
|
||||||
|
name: Gotenberg-Trace
|
||||||
|
description: >-
|
||||||
|
The trace, or request ID, identifies a request in the logs.
|
||||||
|
|
||||||
|
By default, the API generates a UUID trace for each request.
|
||||||
|
However, you may also specify the trace per request, thanks to the Gotenberg-Trace header.
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
required: false
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
multipart/form-data:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
files:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
format: binary
|
||||||
|
pdfFormat:
|
||||||
|
type: string
|
||||||
|
description: The PDF format of the resulting PDF
|
||||||
|
example: PDF/A-1a
|
||||||
|
required:
|
||||||
|
- files
|
||||||
|
- pdfFormat
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
$ref: '#/components/responses/SuccessfulPDF'
|
||||||
|
'400':
|
||||||
|
description: >-
|
||||||
|
Bad Request, e.g. Invalid form data: no form file found for extensions: [.pdf]; form value 'pdfFormat' is required
|
||||||
|
'503':
|
||||||
|
$ref: '#/components/responses/Timeout'
|
||||||
|
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
HTMLConvertRequestBody:
|
||||||
|
title: HTML Conversion Request Body
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
files:
|
||||||
|
type: array
|
||||||
|
description: >-
|
||||||
|
List of HTML files to be converted to PDF. An `index.html` file is
|
||||||
|
required, and any other resources that are referenced through the
|
||||||
|
HTML file must be included as well. All the referenced files must be
|
||||||
|
on the same level as the `index.html` file.
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
format: binary
|
||||||
|
marginTop:
|
||||||
|
type: number
|
||||||
|
example: 0
|
||||||
|
default: 1
|
||||||
|
description: Top margin for the page in inches.
|
||||||
|
marginBottom:
|
||||||
|
type: number
|
||||||
|
example: 0
|
||||||
|
default: 1
|
||||||
|
description: Bottom margin for the page in inches.
|
||||||
|
marginLeft:
|
||||||
|
type: number
|
||||||
|
example: 0
|
||||||
|
default: 1
|
||||||
|
description: Left margin for the page in inches.
|
||||||
|
marginRight:
|
||||||
|
type: number
|
||||||
|
example: 0
|
||||||
|
default: 1
|
||||||
|
description: Right margin for the page in inches.
|
||||||
|
paperWidth:
|
||||||
|
type: number
|
||||||
|
example: 8.27
|
||||||
|
description: >-
|
||||||
|
Paper width to be used while rendering the PDF. The default page
|
||||||
|
size is A4.
|
||||||
|
paperHeight:
|
||||||
|
type: number
|
||||||
|
example: 11.69
|
||||||
|
description: >-
|
||||||
|
Paper height to be used while rendering the PDF. The default page
|
||||||
|
size is A4.
|
||||||
|
preferCssPageSize:
|
||||||
|
type: boolean
|
||||||
|
description: >-
|
||||||
|
Define whether to prefer page size as defined by CSS (default false)
|
||||||
|
default: false
|
||||||
|
printBackground:
|
||||||
|
type: boolean
|
||||||
|
description: >-
|
||||||
|
Print the background graphics (default false)
|
||||||
|
default: false
|
||||||
|
landscape:
|
||||||
|
type: boolean
|
||||||
|
example: true
|
||||||
|
default: false
|
||||||
|
description: >-
|
||||||
|
The default orientation for rendering the page is "portrait" mode.
|
||||||
|
By sending "landscape" parameter, you can ask the output to be
|
||||||
|
landscape.
|
||||||
|
scale:
|
||||||
|
type: number
|
||||||
|
minimum: 0.1
|
||||||
|
maximum: 2.0
|
||||||
|
example: 1.5
|
||||||
|
description: >-
|
||||||
|
The scale of the page rendering
|
||||||
|
default: 1.0
|
||||||
|
waitDelay:
|
||||||
|
type: string
|
||||||
|
example: 5s
|
||||||
|
description: >-
|
||||||
|
When the page relies on JavaScript for rendering, and you don't have access to the page's code,
|
||||||
|
you may want to wait a certain amount of time to make sure Chromium has fully rendered the page
|
||||||
|
you're trying to generate.
|
||||||
|
waitWindowStatus:
|
||||||
|
type: string
|
||||||
|
example: done
|
||||||
|
description: >-
|
||||||
|
If you have access to the page's code, you may set the window status and tell Gotenberg to wait for a specific value.
|
||||||
|
For instance
|
||||||
|
await promises()
|
||||||
|
window.status = 'ready'
|
||||||
|
Prefer this option over waitDelay.
|
||||||
|
extraHttpHeaders:
|
||||||
|
type: string
|
||||||
|
description: HTTP headers to send by Chromium while loading the HTML document (JSON format)
|
||||||
|
nativePageRanges:
|
||||||
|
type: string
|
||||||
|
example: 1-4
|
||||||
|
description: >-
|
||||||
|
The page ranges to be converted to PDF for the incoming Office
|
||||||
|
documents.
|
||||||
|
pdfFormat:
|
||||||
|
type: string
|
||||||
|
description: >-
|
||||||
|
The PDF format of the resulting PDF.
|
||||||
|
Caution! You cannot use both nativePdfA1aFormat and pdfFormat form fields.
|
||||||
|
example: PDF/A-1a
|
||||||
|
required:
|
||||||
|
- files
|
||||||
|
MarkdownConvertRequestBody:
|
||||||
|
title: Markdown Conversion Request Body
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
files:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
format: binary
|
||||||
|
marginTop:
|
||||||
|
type: number
|
||||||
|
example: 0
|
||||||
|
default: 1
|
||||||
|
description: Top margin for the page in inches.
|
||||||
|
marginBottom:
|
||||||
|
type: number
|
||||||
|
example: 0
|
||||||
|
default: 1
|
||||||
|
description: Bottom margin for the page in inches.
|
||||||
|
marginLeft:
|
||||||
|
type: number
|
||||||
|
example: 0
|
||||||
|
default: 1
|
||||||
|
description: Left margin for the page in inches.
|
||||||
|
marginRight:
|
||||||
|
type: number
|
||||||
|
example: 0
|
||||||
|
default: 1
|
||||||
|
description: Right margin for the page in inches.
|
||||||
|
paperWidth:
|
||||||
|
type: number
|
||||||
|
example: 8.27
|
||||||
|
description: >-
|
||||||
|
Paper width to be used while rendering the PDF. The default page
|
||||||
|
size is A4.
|
||||||
|
paperHeight:
|
||||||
|
type: number
|
||||||
|
example: 11.69
|
||||||
|
description: >-
|
||||||
|
Paper height to be used while rendering the PDF. The default page
|
||||||
|
size is A4.
|
||||||
|
preferCssPageSize:
|
||||||
|
type: boolean
|
||||||
|
description: >-
|
||||||
|
Define whether to prefer page size as defined by CSS (default false)
|
||||||
|
default: false
|
||||||
|
printBackground:
|
||||||
|
type: boolean
|
||||||
|
description: >-
|
||||||
|
Print the background graphics (default false)
|
||||||
|
default: false
|
||||||
|
landscape:
|
||||||
|
type: boolean
|
||||||
|
example: true
|
||||||
|
default: false
|
||||||
|
description: >-
|
||||||
|
The default orientation for rendering the page is "portrait" mode.
|
||||||
|
By sending "landscape" parameter, you can ask the output to be
|
||||||
|
landscape.
|
||||||
|
scale:
|
||||||
|
type: number
|
||||||
|
minimum: 0.1
|
||||||
|
maximum: 2.0
|
||||||
|
example: 1.5
|
||||||
|
description: >-
|
||||||
|
The scale of the page rendering
|
||||||
|
default: 1.0
|
||||||
|
waitDelay:
|
||||||
|
type: string
|
||||||
|
example: 5s
|
||||||
|
description: >-
|
||||||
|
When the page relies on JavaScript for rendering, and you don't have access to the page's code,
|
||||||
|
you may want to wait a certain amount of time to make sure Chromium has fully rendered the page
|
||||||
|
you're trying to generate.
|
||||||
|
waitWindowStatus:
|
||||||
|
type: string
|
||||||
|
example: done
|
||||||
|
description: >-
|
||||||
|
If you have access to the page's code, you may set the window status and tell Gotenberg to wait for a specific value.
|
||||||
|
For instance
|
||||||
|
await promises()
|
||||||
|
window.status = 'ready'
|
||||||
|
Prefer this option over waitDelay.
|
||||||
|
extraHttpHeaders:
|
||||||
|
type: string
|
||||||
|
description: HTTP headers to send by Chromium while loading the HTML document (JSON format)
|
||||||
|
nativePageRanges:
|
||||||
|
type: string
|
||||||
|
example: 1-4
|
||||||
|
description: >-
|
||||||
|
The page ranges to be converted to PDF for the incoming Office
|
||||||
|
documents.
|
||||||
|
pdfFormat:
|
||||||
|
type: string
|
||||||
|
description: >-
|
||||||
|
The PDF format of the resulting PDF.
|
||||||
|
Caution! You cannot use both nativePdfA1aFormat and pdfFormat form fields.
|
||||||
|
example: PDF/A-1a
|
||||||
|
required:
|
||||||
|
- files
|
||||||
|
URLConvertRequestBody:
|
||||||
|
title: URL Conversion Request Body
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
url:
|
||||||
|
type: string
|
||||||
|
example: 'https://google.com'
|
||||||
|
files:
|
||||||
|
description: Optional files named header.html and footer.html
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
format: binary
|
||||||
|
marginTop:
|
||||||
|
type: number
|
||||||
|
example: 0
|
||||||
|
default: 1
|
||||||
|
description: Top margin for the page in inches.
|
||||||
|
marginBottom:
|
||||||
|
type: number
|
||||||
|
example: 0
|
||||||
|
default: 1
|
||||||
|
description: Bottom margin for the page in inches.
|
||||||
|
marginLeft:
|
||||||
|
type: number
|
||||||
|
example: 0
|
||||||
|
default: 1
|
||||||
|
description: Left margin for the page in inches.
|
||||||
|
marginRight:
|
||||||
|
type: number
|
||||||
|
example: 0
|
||||||
|
default: 1
|
||||||
|
description: Right margin for the page in inches.
|
||||||
|
paperWidth:
|
||||||
|
type: number
|
||||||
|
example: 8.27
|
||||||
|
description: >-
|
||||||
|
Paper width to be used while rendering the PDF. The default page
|
||||||
|
size is A4.
|
||||||
|
paperHeight:
|
||||||
|
type: number
|
||||||
|
example: 11.69
|
||||||
|
description: >-
|
||||||
|
Paper height to be used while rendering the PDF. The default page
|
||||||
|
size is A4.
|
||||||
|
preferCssPageSize:
|
||||||
|
type: boolean
|
||||||
|
description: >-
|
||||||
|
Define whether to prefer page size as defined by CSS (default false)
|
||||||
|
default: false
|
||||||
|
printBackground:
|
||||||
|
type: boolean
|
||||||
|
description: >-
|
||||||
|
Print the background graphics (default false)
|
||||||
|
default: false
|
||||||
|
landscape:
|
||||||
|
type: boolean
|
||||||
|
example: true
|
||||||
|
default: false
|
||||||
|
description: >-
|
||||||
|
The default orientation for rendering the page is "portrait" mode.
|
||||||
|
By sending "landscape" parameter, you can ask the output to be
|
||||||
|
landscape.
|
||||||
|
scale:
|
||||||
|
type: number
|
||||||
|
minimum: 0.1
|
||||||
|
maximum: 2.0
|
||||||
|
example: 1.5
|
||||||
|
description: >-
|
||||||
|
The scale of the page rendering
|
||||||
|
default: 1.0
|
||||||
|
waitDelay:
|
||||||
|
type: string
|
||||||
|
example: 5s
|
||||||
|
description: >-
|
||||||
|
When the page relies on JavaScript for rendering, and you don't have access to the page's code,
|
||||||
|
you may want to wait a certain amount of time to make sure Chromium has fully rendered the page
|
||||||
|
you're trying to generate.
|
||||||
|
waitWindowStatus:
|
||||||
|
type: string
|
||||||
|
example: done
|
||||||
|
description: >-
|
||||||
|
If you have access to the page's code, you may set the window status and tell Gotenberg to wait for a specific value.
|
||||||
|
For instance
|
||||||
|
await promises()
|
||||||
|
window.status = 'ready'
|
||||||
|
Prefer this option over waitDelay.
|
||||||
|
extraHttpHeaders:
|
||||||
|
type: string
|
||||||
|
description: HTTP headers to send by Chromium while loading the HTML document (JSON format)
|
||||||
|
nativePageRanges:
|
||||||
|
type: string
|
||||||
|
example: 1-4
|
||||||
|
description: >-
|
||||||
|
The page ranges to be converted to PDF for the incoming Office
|
||||||
|
documents.
|
||||||
|
pdfFormat:
|
||||||
|
type: string
|
||||||
|
description: >-
|
||||||
|
The PDF format of the resulting PDF.
|
||||||
|
Caution! You cannot use both nativePdfA1aFormat and pdfFormat form fields.
|
||||||
|
example: PDF/A-1a
|
||||||
|
required:
|
||||||
|
- url
|
||||||
|
OfficeConvertRequestBody:
|
||||||
|
title: Office Conversion Request Body
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
files:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
format: binary
|
||||||
|
nativePageRanges:
|
||||||
|
type: string
|
||||||
|
example: 1-4
|
||||||
|
description: >-
|
||||||
|
The page ranges to be converted to PDF for the incoming Office
|
||||||
|
documents. **If there are multiple files sent to the API, this page
|
||||||
|
range will apply to all of the documents**. Empty means all pages.
|
||||||
|
nativePdfA1aFormat:
|
||||||
|
type: boolean
|
||||||
|
description: >-
|
||||||
|
Use unoconv to convert the resulting PDF to the 'PDF/A-1a' format.
|
||||||
|
Caution! You cannot use both nativePdfA1aFormat and pdfFormat form fields.
|
||||||
|
pdfFormat:
|
||||||
|
type: string
|
||||||
|
description: >-
|
||||||
|
The PDF format of the resulting PDF.
|
||||||
|
Caution! You cannot use both nativePdfA1aFormat and pdfFormat form fields.
|
||||||
|
example: PDF/A-1a
|
||||||
|
landscape:
|
||||||
|
type: boolean
|
||||||
|
example: true
|
||||||
|
default: false
|
||||||
|
description: >-
|
||||||
|
The default orientation for rendering the page is "portrait" mode.
|
||||||
|
By sending "landscape" parameter, you can ask the output to be
|
||||||
|
landscape.
|
||||||
|
merge:
|
||||||
|
type: boolean
|
||||||
|
description: >-
|
||||||
|
Merge all PDF files into an individual PDF file.
|
||||||
|
required:
|
||||||
|
- files
|
||||||
|
MergeFilesRequestBody:
|
||||||
|
title: Merge Files Request Body
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
files:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
format: binary
|
||||||
|
required:
|
||||||
|
- files
|
||||||
|
securitySchemes: { }
|
||||||
|
responses:
|
||||||
|
SuccessfulPDF:
|
||||||
|
description: Resulting PDF file from the conversion.
|
||||||
|
content:
|
||||||
|
application/pdf:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: binary
|
||||||
|
Timeout:
|
||||||
|
description: Service Unavailable
|
||||||
12
go.mod
12
go.mod
@@ -1,4 +1,4 @@
|
|||||||
module github.com/gotenberg/gotenberg/v8
|
module github.com/gotenberg/gotenberg/v7
|
||||||
|
|
||||||
go 1.21
|
go 1.21
|
||||||
|
|
||||||
@@ -13,21 +13,21 @@ require (
|
|||||||
github.com/hashicorp/go-retryablehttp v0.7.5
|
github.com/hashicorp/go-retryablehttp v0.7.5
|
||||||
github.com/klauspost/compress v1.17.4 // indirect
|
github.com/klauspost/compress v1.17.4 // indirect
|
||||||
github.com/klauspost/pgzip v1.2.6 // indirect
|
github.com/klauspost/pgzip v1.2.6 // indirect
|
||||||
github.com/labstack/echo/v4 v4.11.4
|
github.com/labstack/echo/v4 v4.11.3
|
||||||
github.com/labstack/gommon v0.4.2
|
github.com/labstack/gommon v0.4.1
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mholt/archiver/v3 v3.5.1
|
github.com/mholt/archiver/v3 v3.5.1
|
||||||
github.com/microcosm-cc/bluemonday v1.0.26
|
github.com/microcosm-cc/bluemonday v1.0.26
|
||||||
github.com/nwaples/rardecode v1.1.3 // indirect
|
github.com/nwaples/rardecode v1.1.3 // indirect
|
||||||
github.com/pdfcpu/pdfcpu v0.6.0
|
github.com/pdfcpu/pdfcpu v0.6.0
|
||||||
github.com/pierrec/lz4/v4 v4.1.19 // indirect
|
github.com/pierrec/lz4/v4 v4.1.19 // indirect
|
||||||
github.com/prometheus/client_golang v1.18.0
|
github.com/prometheus/client_golang v1.17.0
|
||||||
github.com/russross/blackfriday/v2 v2.1.0
|
github.com/russross/blackfriday/v2 v2.1.0
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/ulikunitz/xz v0.5.11 // indirect
|
github.com/ulikunitz/xz v0.5.11 // indirect
|
||||||
go.uber.org/multierr v1.11.0
|
go.uber.org/multierr v1.11.0
|
||||||
go.uber.org/zap v1.26.0
|
go.uber.org/zap v1.26.0
|
||||||
golang.org/x/crypto v0.17.0 // indirect
|
golang.org/x/crypto v0.16.0 // indirect
|
||||||
golang.org/x/image v0.14.0 // indirect
|
golang.org/x/image v0.14.0 // indirect
|
||||||
golang.org/x/net v0.19.0
|
golang.org/x/net v0.19.0
|
||||||
golang.org/x/sync v0.5.0
|
golang.org/x/sync v0.5.0
|
||||||
@@ -61,6 +61,6 @@ require (
|
|||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
||||||
google.golang.org/protobuf v1.32.0 // indirect
|
google.golang.org/protobuf v1.31.0 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
22
go.sum
22
go.sum
@@ -28,6 +28,7 @@ github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6Wezm
|
|||||||
github.com/gobwas/ws v1.3.0/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
|
github.com/gobwas/ws v1.3.0/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
|
||||||
github.com/gobwas/ws v1.3.1 h1:Qi34dfLMWJbiKaNbDVzM9x27nZBjmkaW6i4+Ku+pGVU=
|
github.com/gobwas/ws v1.3.1 h1:Qi34dfLMWJbiKaNbDVzM9x27nZBjmkaW6i4+Ku+pGVU=
|
||||||
github.com/gobwas/ws v1.3.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
|
github.com/gobwas/ws v1.3.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
|
||||||
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
@@ -62,10 +63,10 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
|||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/labstack/echo/v4 v4.11.4 h1:vDZmA+qNeh1pd/cCkEicDMrjtrnMGQ1QFI9gWN1zGq8=
|
github.com/labstack/echo/v4 v4.11.3 h1:Upyu3olaqSHkCjs1EJJwQ3WId8b8b1hxbogyommKktM=
|
||||||
github.com/labstack/echo/v4 v4.11.4/go.mod h1:noh7EvLwqDsmh/X/HWKPUl1AjzJrhyptRyEbQJfxen8=
|
github.com/labstack/echo/v4 v4.11.3/go.mod h1:UcGuQ8V6ZNRmSweBIJkPvGfwCMIlFmiqrPqiEBfPYws=
|
||||||
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
|
github.com/labstack/gommon v0.4.1 h1:gqEff0p/hTENGMABzezPoPSRtIh1Cvw0ueMOe0/dfOk=
|
||||||
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
|
github.com/labstack/gommon v0.4.1/go.mod h1:TyTrpPqxR5KMk8LKVtLmfMjeQ5FEkBYdxLYPw/WfrOM=
|
||||||
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo=
|
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo=
|
||||||
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
|
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
|
||||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||||
@@ -97,8 +98,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
|
github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=
|
||||||
github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
|
github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY=
|
||||||
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
|
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
|
||||||
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
|
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
|
||||||
github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM=
|
github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM=
|
||||||
@@ -135,8 +136,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
|||||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
|
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
|
||||||
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
|
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
|
||||||
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
|
||||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||||
golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4=
|
golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4=
|
||||||
golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
||||||
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
|
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
|
||||||
@@ -152,8 +153,9 @@ golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
|||||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||||
|
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ type SystemLogger interface {
|
|||||||
// gotenberg.MustRegisterModule(YourModule{})
|
// gotenberg.MustRegisterModule(YourModule{})
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// Then, in the main command (github.com/gotenberg/gotenberg/v8/cmd/gotenberg),
|
// Then, in the main command (github.com/gotenberg/gotenberg/v7/cmd/gotenberg),
|
||||||
// import the module:
|
// import the module:
|
||||||
//
|
//
|
||||||
// imports (
|
// imports (
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import (
|
|||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -30,6 +30,8 @@ func init() {
|
|||||||
// middlewares or health checks.
|
// middlewares or health checks.
|
||||||
type Api struct {
|
type Api struct {
|
||||||
port int
|
port int
|
||||||
|
readTimeout time.Duration
|
||||||
|
writeTimeout time.Duration
|
||||||
startTimeout time.Duration
|
startTimeout time.Duration
|
||||||
timeout time.Duration
|
timeout time.Duration
|
||||||
rootPath string
|
rootPath string
|
||||||
@@ -160,11 +162,23 @@ func (a *Api) Descriptor() gotenberg.ModuleDescriptor {
|
|||||||
fs.Int("api-port", 3000, "Set the port on which the API should listen")
|
fs.Int("api-port", 3000, "Set the port on which the API should listen")
|
||||||
fs.String("api-port-from-env", "", "Set the environment variable with the port on which the API should listen - override the default port")
|
fs.String("api-port-from-env", "", "Set the environment variable with the port on which the API should listen - override the default port")
|
||||||
fs.Duration("api-start-timeout", time.Duration(30)*time.Second, "Set the time limit for the API to start")
|
fs.Duration("api-start-timeout", time.Duration(30)*time.Second, "Set the time limit for the API to start")
|
||||||
|
fs.Duration("api-read-timeout", time.Duration(30)*time.Second, "Set the maximum duration allowed to read a complete request, including the body")
|
||||||
|
fs.Duration("api-process-timeout", time.Duration(30)*time.Second, "Set the maximum duration allowed to process a request")
|
||||||
|
fs.Duration("api-write-timeout", time.Duration(30)*time.Second, "Set the maximum duration before timing out writes of the response")
|
||||||
fs.Duration("api-timeout", time.Duration(30)*time.Second, "Set the time limit for requests")
|
fs.Duration("api-timeout", time.Duration(30)*time.Second, "Set the time limit for requests")
|
||||||
fs.String("api-root-path", "/", "Set the root path of the API - for service discovery via URL paths")
|
fs.String("api-root-path", "/", "Set the root path of the API - for service discovery via URL paths")
|
||||||
fs.String("api-trace-header", "Gotenberg-Trace", "Set the header name to use for identifying requests")
|
fs.String("api-trace-header", "Gotenberg-Trace", "Set the header name to use for identifying requests")
|
||||||
fs.Bool("api-disable-health-check-logging", false, "Disable health check logging")
|
fs.Bool("api-disable-health-check-logging", false, "Disable health check logging")
|
||||||
|
|
||||||
|
var err error
|
||||||
|
err = multierr.Append(err, fs.MarkDeprecated("api-read-timeout", "use api-timeout instead"))
|
||||||
|
err = multierr.Append(err, fs.MarkDeprecated("api-process-timeout", "use api-timeout instead"))
|
||||||
|
err = multierr.Append(err, fs.MarkDeprecated("api-write-timeout", "use api-timeout instead"))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("create deprecated flags for the api module: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
return fs
|
return fs
|
||||||
}(),
|
}(),
|
||||||
New: func() gotenberg.Module { return new(Api) },
|
New: func() gotenberg.Module { return new(Api) },
|
||||||
@@ -176,7 +190,9 @@ func (a *Api) Provision(ctx *gotenberg.Context) error {
|
|||||||
flags := ctx.ParsedFlags()
|
flags := ctx.ParsedFlags()
|
||||||
a.port = flags.MustInt("api-port")
|
a.port = flags.MustInt("api-port")
|
||||||
a.startTimeout = flags.MustDuration("api-start-timeout")
|
a.startTimeout = flags.MustDuration("api-start-timeout")
|
||||||
a.timeout = flags.MustDuration("api-timeout")
|
a.readTimeout = flags.MustDeprecatedDuration("api-read-timeout", "api-timeout")
|
||||||
|
a.writeTimeout = flags.MustDeprecatedDuration("api-write-timeout", "api-timeout")
|
||||||
|
a.timeout = flags.MustDeprecatedDuration("api-process-timeout", "api-timeout")
|
||||||
a.rootPath = flags.MustString("api-root-path")
|
a.rootPath = flags.MustString("api-root-path")
|
||||||
a.traceHeader = flags.MustString("api-trace-header")
|
a.traceHeader = flags.MustString("api-trace-header")
|
||||||
a.disableHealthCheckLogging = flags.MustBool("api-disable-health-check-logging")
|
a.disableHealthCheckLogging = flags.MustBool("api-disable-health-check-logging")
|
||||||
@@ -364,10 +380,10 @@ func (a *Api) Start() error {
|
|||||||
a.srv = echo.New()
|
a.srv = echo.New()
|
||||||
a.srv.HideBanner = true
|
a.srv.HideBanner = true
|
||||||
a.srv.HidePort = true
|
a.srv.HidePort = true
|
||||||
a.srv.Server.ReadTimeout = a.timeout
|
a.srv.Server.ReadTimeout = a.readTimeout
|
||||||
a.srv.Server.IdleTimeout = a.timeout
|
a.srv.Server.IdleTimeout = a.timeout
|
||||||
// See https://github.com/gotenberg/gotenberg/issues/396.
|
// See https://github.com/gotenberg/gotenberg/issues/396.
|
||||||
a.srv.Server.WriteTimeout = a.timeout + a.timeout
|
a.srv.Server.WriteTimeout = a.writeTimeout + a.writeTimeout
|
||||||
a.srv.HTTPErrorHandler = httpErrorHandler()
|
a.srv.HTTPErrorHandler = httpErrorHandler()
|
||||||
|
|
||||||
// Let's prepare the modules' routes.
|
// Let's prepare the modules' routes.
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import (
|
|||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestApi_Descriptor(t *testing.T) {
|
func TestApi_Descriptor(t *testing.T) {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import (
|
|||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"golang.org/x/text/unicode/norm"
|
"golang.org/x/text/unicode/norm"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import (
|
|||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewContext(t *testing.T) {
|
func TestNewContext(t *testing.T) {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrAsyncProcess happens when a handler or middleware handles a request in an
|
// ErrAsyncProcess happens when a handler or middleware handles a request in an
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParseError(t *testing.T) {
|
func TestParseError(t *testing.T) {
|
||||||
|
|||||||
@@ -17,13 +17,12 @@ import (
|
|||||||
"github.com/chromedp/chromedp"
|
"github.com/chromedp/chromedp"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||||
)
|
)
|
||||||
|
|
||||||
type browser interface {
|
type browser interface {
|
||||||
gotenberg.Process
|
gotenberg.Process
|
||||||
pdf(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error
|
pdf(ctx context.Context, logger *zap.Logger, url, outputPath string, options Options) error
|
||||||
screenshot(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type browserArguments struct {
|
type browserArguments struct {
|
||||||
@@ -41,8 +40,6 @@ type browserArguments struct {
|
|||||||
// Tasks specific.
|
// Tasks specific.
|
||||||
allowList *regexp.Regexp
|
allowList *regexp.Regexp
|
||||||
denyList *regexp.Regexp
|
denyList *regexp.Regexp
|
||||||
clearCache bool
|
|
||||||
clearCookies bool
|
|
||||||
disableJavaScript bool
|
disableJavaScript bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,53 +211,9 @@ func (b *chromiumBrowser) Healthy(logger *zap.Logger) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *chromiumBrowser) pdf(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error {
|
func (b *chromiumBrowser) pdf(ctx context.Context, logger *zap.Logger, url, outputPath string, options Options) error {
|
||||||
// Note: no error wrapping because it leaks on errors we want to display to
|
|
||||||
// the end user.
|
|
||||||
return b.do(ctx, logger, url, options.Options, chromedp.Tasks{
|
|
||||||
network.Enable(),
|
|
||||||
fetch.Enable(),
|
|
||||||
runtime.Enable(),
|
|
||||||
clearCacheActionFunc(logger, b.arguments.clearCache),
|
|
||||||
clearCookiesActionFunc(logger, b.arguments.clearCookies),
|
|
||||||
disableJavaScriptActionFunc(logger, b.arguments.disableJavaScript),
|
|
||||||
extraHttpHeadersActionFunc(logger, options.ExtraHttpHeaders),
|
|
||||||
navigateActionFunc(logger, url, options.SkipNetworkIdleEvent),
|
|
||||||
hideDefaultWhiteBackgroundActionFunc(logger, options.OmitBackground, options.PrintBackground),
|
|
||||||
forceExactColorsActionFunc(),
|
|
||||||
emulateMediaTypeActionFunc(logger, options.EmulatedMediaType),
|
|
||||||
waitDelayBeforePrintActionFunc(logger, b.arguments.disableJavaScript, options.WaitDelay),
|
|
||||||
waitForExpressionBeforePrintActionFunc(logger, b.arguments.disableJavaScript, options.WaitForExpression),
|
|
||||||
// PDF specific.
|
|
||||||
printToPdfActionFunc(logger, outputPath, options),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *chromiumBrowser) screenshot(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error {
|
|
||||||
// Note: no error wrapping because it leaks on errors we want to display to
|
|
||||||
// the end user.
|
|
||||||
return b.do(ctx, logger, url, options.Options, chromedp.Tasks{
|
|
||||||
network.Enable(),
|
|
||||||
fetch.Enable(),
|
|
||||||
runtime.Enable(),
|
|
||||||
clearCacheActionFunc(logger, b.arguments.clearCache),
|
|
||||||
clearCookiesActionFunc(logger, b.arguments.clearCookies),
|
|
||||||
disableJavaScriptActionFunc(logger, b.arguments.disableJavaScript),
|
|
||||||
extraHttpHeadersActionFunc(logger, options.ExtraHttpHeaders),
|
|
||||||
navigateActionFunc(logger, url, options.SkipNetworkIdleEvent),
|
|
||||||
hideDefaultWhiteBackgroundActionFunc(logger, options.OmitBackground, true),
|
|
||||||
forceExactColorsActionFunc(),
|
|
||||||
emulateMediaTypeActionFunc(logger, options.EmulatedMediaType),
|
|
||||||
waitDelayBeforePrintActionFunc(logger, b.arguments.disableJavaScript, options.WaitDelay),
|
|
||||||
waitForExpressionBeforePrintActionFunc(logger, b.arguments.disableJavaScript, options.WaitForExpression),
|
|
||||||
// Screenshot specific.
|
|
||||||
captureScreenshotActionFunc(logger, outputPath, options),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *chromiumBrowser) do(ctx context.Context, logger *zap.Logger, url string, options Options, tasks chromedp.Tasks) error {
|
|
||||||
if !b.isStarted.Load() {
|
if !b.isStarted.Load() {
|
||||||
return errors.New("browser not started, cannot handle tasks")
|
return errors.New("browser not started, cannot handle PDF conversion")
|
||||||
}
|
}
|
||||||
|
|
||||||
// We validate the "main" URL against our allow / deny lists.
|
// We validate the "main" URL against our allow / deny lists.
|
||||||
@@ -290,16 +243,6 @@ func (b *chromiumBrowser) do(ctx context.Context, logger *zap.Logger, url string
|
|||||||
// If a request does not pass the validation, we make it fail.
|
// If a request does not pass the validation, we make it fail.
|
||||||
listenForEventRequestPaused(taskCtx, logger, b.arguments.allowList, b.arguments.denyList)
|
listenForEventRequestPaused(taskCtx, logger, b.arguments.allowList, b.arguments.denyList)
|
||||||
|
|
||||||
var (
|
|
||||||
invalidHttpStatusCode error
|
|
||||||
invalidHttpStatusCodeMu sync.RWMutex
|
|
||||||
)
|
|
||||||
|
|
||||||
// See https://github.com/gotenberg/gotenberg/issues/613.
|
|
||||||
if len(options.FailOnHttpStatusCodes) != 0 {
|
|
||||||
listenForEventResponseReceived(taskCtx, logger, url, options.FailOnHttpStatusCodes, &invalidHttpStatusCode, &invalidHttpStatusCodeMu)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
consoleExceptions error
|
consoleExceptions error
|
||||||
consoleExceptionsMu sync.RWMutex
|
consoleExceptionsMu sync.RWMutex
|
||||||
@@ -310,6 +253,21 @@ func (b *chromiumBrowser) do(ctx context.Context, logger *zap.Logger, url string
|
|||||||
listenForEventExceptionThrown(taskCtx, logger, &consoleExceptions, &consoleExceptionsMu)
|
listenForEventExceptionThrown(taskCtx, logger, &consoleExceptions, &consoleExceptionsMu)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tasks := chromedp.Tasks{
|
||||||
|
network.Enable(),
|
||||||
|
fetch.Enable(),
|
||||||
|
runtime.Enable(),
|
||||||
|
disableJavaScriptActionFunc(logger, b.arguments.disableJavaScript),
|
||||||
|
extraHttpHeadersActionFunc(logger, options.ExtraHttpHeaders),
|
||||||
|
navigateActionFunc(logger, url),
|
||||||
|
hideDefaultWhiteBackgroundActionFunc(logger, options.OmitBackground, options.PrintBackground),
|
||||||
|
forceExactColorsActionFunc(),
|
||||||
|
emulateMediaTypeActionFunc(logger, options.EmulatedMediaType),
|
||||||
|
waitDelayBeforePrintActionFunc(logger, b.arguments.disableJavaScript, options.WaitDelay),
|
||||||
|
waitForExpressionBeforePrintActionFunc(logger, b.arguments.disableJavaScript, options.WaitForExpression),
|
||||||
|
printToPdfActionFunc(logger, outputPath, options),
|
||||||
|
}
|
||||||
|
|
||||||
err := chromedp.Run(taskCtx, tasks...)
|
err := chromedp.Run(taskCtx, tasks...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errMessage := err.Error()
|
errMessage := err.Error()
|
||||||
@@ -326,15 +284,7 @@ func (b *chromiumBrowser) do(ctx context.Context, logger *zap.Logger, url string
|
|||||||
return ErrRpccMessageTooLarge
|
return ErrRpccMessageTooLarge
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("handle tasks: %w", err)
|
return fmt.Errorf("print to PDF: %w", err)
|
||||||
}
|
|
||||||
|
|
||||||
// See https://github.com/gotenberg/gotenberg/issues/613.
|
|
||||||
invalidHttpStatusCodeMu.RLock()
|
|
||||||
defer invalidHttpStatusCodeMu.RUnlock()
|
|
||||||
|
|
||||||
if invalidHttpStatusCode != nil {
|
|
||||||
return fmt.Errorf("%v: %w", invalidHttpStatusCode, ErrInvalidHttpStatusCode)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// See https://github.com/gotenberg/gotenberg/issues/262.
|
// See https://github.com/gotenberg/gotenberg/issues/262.
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -9,10 +9,11 @@ import (
|
|||||||
|
|
||||||
"github.com/alexliesenfeld/health"
|
"github.com/alexliesenfeld/health"
|
||||||
flag "github.com/spf13/pflag"
|
flag "github.com/spf13/pflag"
|
||||||
|
"go.uber.org/multierr"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/modules/api"
|
"github.com/gotenberg/gotenberg/v7/pkg/modules/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -24,6 +25,10 @@ var (
|
|||||||
// allowed/denied lists.
|
// allowed/denied lists.
|
||||||
ErrUrlNotAuthorized = errors.New("URL not authorized")
|
ErrUrlNotAuthorized = errors.New("URL not authorized")
|
||||||
|
|
||||||
|
// ErrOmitBackgroundWithoutPrintBackground happens if
|
||||||
|
// Options.OmitBackground is set to true but not Options.PrintBackground.
|
||||||
|
ErrOmitBackgroundWithoutPrintBackground = errors.New("omit background without print background")
|
||||||
|
|
||||||
// ErrInvalidEmulatedMediaType happens if the emulated media type is not
|
// ErrInvalidEmulatedMediaType happens if the emulated media type is not
|
||||||
// "screen" nor "print". Empty value are allowed though.
|
// "screen" nor "print". Empty value are allowed though.
|
||||||
ErrInvalidEmulatedMediaType = errors.New("invalid emulated media type")
|
ErrInvalidEmulatedMediaType = errors.New("invalid emulated media type")
|
||||||
@@ -32,32 +37,22 @@ var (
|
|||||||
// returns an exception or undefined.
|
// returns an exception or undefined.
|
||||||
ErrInvalidEvaluationExpression = errors.New("invalid evaluation expression")
|
ErrInvalidEvaluationExpression = errors.New("invalid evaluation expression")
|
||||||
|
|
||||||
|
// ErrInvalidPrinterSettings happens if the Options have one or more
|
||||||
|
// aberrant values.
|
||||||
|
ErrInvalidPrinterSettings = errors.New("invalid printer settings")
|
||||||
|
|
||||||
|
// ErrPageRangesSyntaxError happens if the Options have an invalid page
|
||||||
|
// ranges.
|
||||||
|
ErrPageRangesSyntaxError = errors.New("page ranges syntax error")
|
||||||
|
|
||||||
// ErrRpccMessageTooLarge happens when the messages received by
|
// ErrRpccMessageTooLarge happens when the messages received by
|
||||||
// ChromeDevTools are larger than 100 MB.
|
// ChromeDevTools are larger than 100 MB.
|
||||||
ErrRpccMessageTooLarge = errors.New("rpcc message too large")
|
ErrRpccMessageTooLarge = errors.New("rpcc message too large")
|
||||||
|
|
||||||
// ErrInvalidHttpStatusCode happens when the status code from the main page
|
|
||||||
// matches with one of the entry in [Options.FailOnHttpStatusCodes].
|
|
||||||
ErrInvalidHttpStatusCode = errors.New("invalid HTTP status code")
|
|
||||||
|
|
||||||
// ErrConsoleExceptions happens when there are exceptions in the Chromium
|
// ErrConsoleExceptions happens when there are exceptions in the Chromium
|
||||||
// console. It also happens only if the [Options.FailOnConsoleExceptions]
|
// console. It also happens only if the [Options.FailOnConsoleExceptions]
|
||||||
// is set to true.
|
// is set to true.
|
||||||
ErrConsoleExceptions = errors.New("console exceptions")
|
ErrConsoleExceptions = errors.New("console exceptions")
|
||||||
|
|
||||||
// PDF specific.
|
|
||||||
|
|
||||||
// ErrOmitBackgroundWithoutPrintBackground happens if
|
|
||||||
// PdfOptions.OmitBackground is set to true but not PdfOptions.PrintBackground.
|
|
||||||
ErrOmitBackgroundWithoutPrintBackground = errors.New("omit background without print background")
|
|
||||||
|
|
||||||
// ErrInvalidPrinterSettings happens if the PdfOptions have one or more
|
|
||||||
// aberrant values.
|
|
||||||
ErrInvalidPrinterSettings = errors.New("invalid printer settings")
|
|
||||||
|
|
||||||
// ErrPageRangesSyntaxError happens if the PdfOptions have an invalid page
|
|
||||||
// ranges.
|
|
||||||
ErrPageRangesSyntaxError = errors.New("page ranges syntax error")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Chromium is a module which provides both an [Api] and routes for converting
|
// Chromium is a module which provides both an [Api] and routes for converting
|
||||||
@@ -73,37 +68,25 @@ type Chromium struct {
|
|||||||
engine gotenberg.PdfEngine
|
engine gotenberg.PdfEngine
|
||||||
}
|
}
|
||||||
|
|
||||||
// Options are the common options for all conversions.
|
// Options are the available expectedOptions for converting HTML document to PDF.
|
||||||
type Options struct {
|
type Options struct {
|
||||||
// SkipNetworkIdleEvent set if the conversion should wait for the
|
|
||||||
// "networkIdle" event, drastically improving the conversion speed. It may
|
|
||||||
// not be suitable for all HTML documents, as some may not be fully
|
|
||||||
// rendered until this event is fired.
|
|
||||||
// Optional.
|
|
||||||
SkipNetworkIdleEvent bool
|
|
||||||
|
|
||||||
// FailOnHttpStatusCodes sets if the conversion should fail if the status
|
|
||||||
// code from the main page matches with one of its entries.
|
|
||||||
// Optional.
|
|
||||||
FailOnHttpStatusCodes []int64
|
|
||||||
|
|
||||||
// FailOnConsoleExceptions sets if the conversion should fail if there are
|
// FailOnConsoleExceptions sets if the conversion should fail if there are
|
||||||
// exceptions in the Chromium console.
|
// exceptions in the Chromium console.
|
||||||
// Optional.
|
// Optional.
|
||||||
FailOnConsoleExceptions bool
|
FailOnConsoleExceptions bool
|
||||||
|
|
||||||
// WaitDelay is the duration to wait when loading an HTML document before
|
// WaitDelay is the duration to wait when loading an HTML document before
|
||||||
// converting it.
|
// converting it to PDF.
|
||||||
// Optional.
|
// Optional.
|
||||||
WaitDelay time.Duration
|
WaitDelay time.Duration
|
||||||
|
|
||||||
// WaitWindowStatus is the window.status value to wait for before
|
// WaitWindowStatus is the window.status value to wait for before
|
||||||
// converting an HTML document.
|
// converting an HTML document to PDF.
|
||||||
// Optional.
|
// Optional.
|
||||||
WaitWindowStatus string
|
WaitWindowStatus string
|
||||||
|
|
||||||
// WaitForExpression is the custom JavaScript expression to wait before
|
// WaitForExpression is the custom JavaScript expression to wait before
|
||||||
// converting an HTML document until it returns true
|
// converting an HTML document to PDF until it returns true
|
||||||
// Optional.
|
// Optional.
|
||||||
WaitForExpression string
|
WaitForExpression string
|
||||||
|
|
||||||
@@ -117,31 +100,6 @@ type Options struct {
|
|||||||
// Optional.
|
// Optional.
|
||||||
EmulatedMediaType string
|
EmulatedMediaType string
|
||||||
|
|
||||||
// OmitBackground hides default white background and allows generating PDFs
|
|
||||||
// with transparency.
|
|
||||||
// Optional.
|
|
||||||
OmitBackground bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultOptions returns the default values for Options.
|
|
||||||
func DefaultOptions() Options {
|
|
||||||
return Options{
|
|
||||||
SkipNetworkIdleEvent: false,
|
|
||||||
FailOnHttpStatusCodes: []int64{499, 599},
|
|
||||||
FailOnConsoleExceptions: false,
|
|
||||||
WaitDelay: 0,
|
|
||||||
WaitWindowStatus: "",
|
|
||||||
WaitForExpression: "",
|
|
||||||
ExtraHttpHeaders: nil,
|
|
||||||
EmulatedMediaType: "",
|
|
||||||
OmitBackground: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PdfOptions are the available options for converting an HTML document to PDF.
|
|
||||||
type PdfOptions struct {
|
|
||||||
Options
|
|
||||||
|
|
||||||
// Landscape sets the paper orientation.
|
// Landscape sets the paper orientation.
|
||||||
// Optional.
|
// Optional.
|
||||||
Landscape bool
|
Landscape bool
|
||||||
@@ -150,6 +108,11 @@ type PdfOptions struct {
|
|||||||
// Optional.
|
// Optional.
|
||||||
PrintBackground bool
|
PrintBackground bool
|
||||||
|
|
||||||
|
// OmitBackground hides default white background and allows generating PDFs
|
||||||
|
// with transparency.
|
||||||
|
// Optional.
|
||||||
|
OmitBackground bool
|
||||||
|
|
||||||
// Scale is the scale of the page rendering.
|
// Scale is the scale of the page rendering.
|
||||||
// Optional.
|
// Optional.
|
||||||
Scale float64
|
Scale float64
|
||||||
@@ -206,60 +169,35 @@ type PdfOptions struct {
|
|||||||
PreferCssPageSize bool
|
PreferCssPageSize bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultPdfOptions returns the default values for PdfOptions.
|
// DefaultOptions returns the default values for Options.
|
||||||
func DefaultPdfOptions() PdfOptions {
|
func DefaultOptions() Options {
|
||||||
return PdfOptions{
|
return Options{
|
||||||
Options: DefaultOptions(),
|
FailOnConsoleExceptions: false,
|
||||||
Landscape: false,
|
WaitDelay: 0,
|
||||||
PrintBackground: false,
|
WaitWindowStatus: "",
|
||||||
Scale: 1.0,
|
WaitForExpression: "",
|
||||||
PaperWidth: 8.5,
|
ExtraHttpHeaders: nil,
|
||||||
PaperHeight: 11,
|
EmulatedMediaType: "",
|
||||||
MarginTop: 0.39,
|
Landscape: false,
|
||||||
MarginBottom: 0.39,
|
PrintBackground: false,
|
||||||
MarginLeft: 0.39,
|
OmitBackground: false,
|
||||||
MarginRight: 0.39,
|
Scale: 1.0,
|
||||||
PageRanges: "",
|
PaperWidth: 8.5,
|
||||||
HeaderTemplate: "<html><head></head><body></body></html>",
|
PaperHeight: 11,
|
||||||
FooterTemplate: "<html><head></head><body></body></html>",
|
MarginTop: 0.39,
|
||||||
PreferCssPageSize: false,
|
MarginBottom: 0.39,
|
||||||
}
|
MarginLeft: 0.39,
|
||||||
}
|
MarginRight: 0.39,
|
||||||
|
PageRanges: "",
|
||||||
// ScreenshotOptions are the available options for capturing a screenshot from
|
HeaderTemplate: "<html><head></head><body></body></html>",
|
||||||
// an HTML document.
|
FooterTemplate: "<html><head></head><body></body></html>",
|
||||||
type ScreenshotOptions struct {
|
PreferCssPageSize: false,
|
||||||
Options
|
|
||||||
|
|
||||||
// Format is the image compression format, either "png" or "jpeg" or
|
|
||||||
// "webp".
|
|
||||||
// Optional.
|
|
||||||
Format string
|
|
||||||
|
|
||||||
// Quality is the compression quality from range [0..100] (jpeg only).
|
|
||||||
// Optional.
|
|
||||||
Quality int
|
|
||||||
|
|
||||||
// OptimizeForSpeed defines whether to optimize image encoding for speed,
|
|
||||||
// not for resulting size.
|
|
||||||
// Optional.
|
|
||||||
OptimizeForSpeed bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultScreenshotOptions returns the default values for ScreenshotOptions.
|
|
||||||
func DefaultScreenshotOptions() ScreenshotOptions {
|
|
||||||
return ScreenshotOptions{
|
|
||||||
Options: DefaultOptions(),
|
|
||||||
Format: "png",
|
|
||||||
Quality: 100,
|
|
||||||
OptimizeForSpeed: false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Api helps to interact with Chromium for converting HTML documents to PDF.
|
// Api helps to interact with Chromium for converting HTML documents to PDF.
|
||||||
type Api interface {
|
type Api interface {
|
||||||
Pdf(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error
|
Pdf(ctx context.Context, logger *zap.Logger, url, outputPath string, options Options) error
|
||||||
Screenshot(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provider is a module interface which exposes a method for creating an [Api]
|
// Provider is a module interface which exposes a method for creating an [Api]
|
||||||
@@ -279,6 +217,18 @@ func (mod *Chromium) Descriptor() gotenberg.ModuleDescriptor {
|
|||||||
ID: "chromium",
|
ID: "chromium",
|
||||||
FlagSet: func() *flag.FlagSet {
|
FlagSet: func() *flag.FlagSet {
|
||||||
fs := flag.NewFlagSet("chromium", flag.ExitOnError)
|
fs := flag.NewFlagSet("chromium", flag.ExitOnError)
|
||||||
|
// Deprecated flags.
|
||||||
|
fs.String("chromium-user-agent", "", "Override the default User-Agent header")
|
||||||
|
fs.Int("chromium-failed-starts-threshold", 5, "Set the number of consecutive failed starts after which the module is considered unhealthy - 0 means ignore")
|
||||||
|
|
||||||
|
var err error
|
||||||
|
err = multierr.Append(err, fs.MarkDeprecated("chromium-user-agent", "use the extraHttpHeaders form field instead"))
|
||||||
|
err = multierr.Append(err, fs.MarkDeprecated("chromium-failed-starts-threshold", "use the chromium-restart-after property instead"))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("create deprecated flags for the Chromium module: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
fs.Int64("chromium-restart-after", 0, "Number of conversions after which Chromium will automatically restart. Set to 0 to disable this feature")
|
fs.Int64("chromium-restart-after", 0, "Number of conversions after which Chromium will automatically restart. Set to 0 to disable this feature")
|
||||||
fs.Bool("chromium-auto-start", false, "Automatically launch Chromium upon initialization if set to true; otherwise, Chromium will start at the time of the first conversion")
|
fs.Bool("chromium-auto-start", false, "Automatically launch Chromium upon initialization if set to true; otherwise, Chromium will start at the time of the first conversion")
|
||||||
fs.Duration("chromium-start-timeout", time.Duration(20)*time.Second, "Maximum duration to wait for Chromium to start or restart")
|
fs.Duration("chromium-start-timeout", time.Duration(20)*time.Second, "Maximum duration to wait for Chromium to start or restart")
|
||||||
@@ -291,8 +241,6 @@ func (mod *Chromium) Descriptor() gotenberg.ModuleDescriptor {
|
|||||||
fs.String("chromium-proxy-server", "", "Set the outbound proxy server; this switch only affects HTTP and HTTPS requests")
|
fs.String("chromium-proxy-server", "", "Set the outbound proxy server; this switch only affects HTTP and HTTPS requests")
|
||||||
fs.String("chromium-allow-list", "", "Set the allowed URLs for Chromium using a regular expression")
|
fs.String("chromium-allow-list", "", "Set the allowed URLs for Chromium using a regular expression")
|
||||||
fs.String("chromium-deny-list", "^file:///[^tmp].*", "Set the denied URLs for Chromium using a regular expression")
|
fs.String("chromium-deny-list", "^file:///[^tmp].*", "Set the denied URLs for Chromium using a regular expression")
|
||||||
fs.Bool("chromium-clear-cache", false, "Clear Chromium cache between each conversion")
|
|
||||||
fs.Bool("chromium-clear-cookies", false, "Clear Chromium cookies between each conversion")
|
|
||||||
fs.Bool("chromium-disable-javascript", false, "Disable JavaScript")
|
fs.Bool("chromium-disable-javascript", false, "Disable JavaScript")
|
||||||
fs.Bool("chromium-disable-routes", false, "Disable the routes")
|
fs.Bool("chromium-disable-routes", false, "Disable the routes")
|
||||||
|
|
||||||
@@ -326,8 +274,6 @@ func (mod *Chromium) Provision(ctx *gotenberg.Context) error {
|
|||||||
|
|
||||||
allowList: flags.MustRegexp("chromium-allow-list"),
|
allowList: flags.MustRegexp("chromium-allow-list"),
|
||||||
denyList: flags.MustRegexp("chromium-deny-list"),
|
denyList: flags.MustRegexp("chromium-deny-list"),
|
||||||
clearCache: flags.MustBool("chromium-clear-cache"),
|
|
||||||
clearCookies: flags.MustBool("chromium-clear-cookies"),
|
|
||||||
disableJavaScript: flags.MustBool("chromium-disable-javascript"),
|
disableJavaScript: flags.MustBool("chromium-disable-javascript"),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -413,6 +359,22 @@ func (mod *Chromium) Stop(ctx context.Context) error {
|
|||||||
// Metrics returns the metrics.
|
// Metrics returns the metrics.
|
||||||
func (mod *Chromium) Metrics() ([]gotenberg.Metric, error) {
|
func (mod *Chromium) Metrics() ([]gotenberg.Metric, error) {
|
||||||
return []gotenberg.Metric{
|
return []gotenberg.Metric{
|
||||||
|
{
|
||||||
|
// TODO: remove deprecated.
|
||||||
|
Name: "chromium_active_instances_count",
|
||||||
|
Description: "Current number of active Chromium instances - deprecated.",
|
||||||
|
Read: func() float64 {
|
||||||
|
return 1
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// TODO: remove deprecated.
|
||||||
|
Name: "chromium_failed_starts_count",
|
||||||
|
Description: "Current number of Chromium consecutive starting failures - deprecated.",
|
||||||
|
Read: func() float64 {
|
||||||
|
return 0
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "chromium_requests_queue_size",
|
Name: "chromium_requests_queue_size",
|
||||||
Description: "Current number of Chromium conversion requests waiting to be treated.",
|
Description: "Current number of Chromium conversion requests waiting to be treated.",
|
||||||
@@ -488,31 +450,19 @@ func (mod *Chromium) Routes() ([]api.Route, error) {
|
|||||||
|
|
||||||
return []api.Route{
|
return []api.Route{
|
||||||
convertUrlRoute(mod, mod.engine),
|
convertUrlRoute(mod, mod.engine),
|
||||||
screenshotUrlRoute(mod),
|
|
||||||
convertHtmlRoute(mod, mod.engine),
|
convertHtmlRoute(mod, mod.engine),
|
||||||
screenshotHtmlRoute(mod),
|
|
||||||
convertMarkdownRoute(mod, mod.engine),
|
convertMarkdownRoute(mod, mod.engine),
|
||||||
screenshotMarkdownRoute(mod),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pdf converts a URL to PDF.
|
// Pdf converts a URL to PDF.
|
||||||
func (mod *Chromium) Pdf(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error {
|
func (mod *Chromium) Pdf(ctx context.Context, logger *zap.Logger, url, outputPath string, options Options) error {
|
||||||
// Note: no error wrapping because it leaks on errors we want to display to
|
// FIXME: no error wrapping because it leaks on console exceptions output.
|
||||||
// the end user.
|
|
||||||
return mod.supervisor.Run(ctx, logger, func() error {
|
return mod.supervisor.Run(ctx, logger, func() error {
|
||||||
return mod.browser.pdf(ctx, logger, url, outputPath, options)
|
return mod.browser.pdf(ctx, logger, url, outputPath, options)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mod *Chromium) Screenshot(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error {
|
|
||||||
// Note: no error wrapping because it leaks on errors we want to display to
|
|
||||||
// the end user.
|
|
||||||
return mod.supervisor.Run(ctx, logger, func() error {
|
|
||||||
return mod.browser.screenshot(ctx, logger, url, outputPath, options)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interface guards.
|
// Interface guards.
|
||||||
var (
|
var (
|
||||||
_ gotenberg.Module = (*Chromium)(nil)
|
_ gotenberg.Module = (*Chromium)(nil)
|
||||||
|
|||||||
@@ -11,12 +11,12 @@ import (
|
|||||||
"github.com/alexliesenfeld/health"
|
"github.com/alexliesenfeld/health"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDefaultOptions(t *testing.T) {
|
func TestDefaultOptions(t *testing.T) {
|
||||||
actual := DefaultPdfOptions()
|
actual := DefaultOptions()
|
||||||
notExpect := PdfOptions{}
|
notExpect := Options{}
|
||||||
|
|
||||||
if reflect.DeepEqual(actual, notExpect) {
|
if reflect.DeepEqual(actual, notExpect) {
|
||||||
t.Errorf("expected %v and got identical %v", actual, notExpect)
|
t.Errorf("expected %v and got identical %v", actual, notExpect)
|
||||||
@@ -334,16 +334,26 @@ func TestChromium_Metrics(t *testing.T) {
|
|||||||
t.Fatalf("expected no error but got: %v", err)
|
t.Fatalf("expected no error but got: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(metrics) != 2 {
|
if len(metrics) != 4 {
|
||||||
t.Fatalf("expected %d metrics, but got %d", 2, len(metrics))
|
t.Fatalf("expected %d metrics, but got %d", 4, len(metrics))
|
||||||
}
|
}
|
||||||
|
|
||||||
actual := metrics[0].Read()
|
actual := metrics[0].Read()
|
||||||
|
if actual != float64(1) {
|
||||||
|
t.Errorf("expected %f for chromium_active_instances_count, but got %f", float64(1), actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual = metrics[1].Read()
|
||||||
|
if actual != float64(0) {
|
||||||
|
t.Errorf("expected %f for chromium_failed_starts_count, but got %f", float64(0), actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual = metrics[2].Read()
|
||||||
if actual != float64(10) {
|
if actual != float64(10) {
|
||||||
t.Errorf("expected %f for chromium_requests_queue_size, but got %f", float64(10), actual)
|
t.Errorf("expected %f for chromium_requests_queue_size, but got %f", float64(10), actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
actual = metrics[1].Read()
|
actual = metrics[3].Read()
|
||||||
if actual != float64(0) {
|
if actual != float64(0) {
|
||||||
t.Errorf("expected %f for chromium_restarts_count, but got %f", float64(0), actual)
|
t.Errorf("expected %f for chromium_restarts_count, but got %f", float64(0), actual)
|
||||||
}
|
}
|
||||||
@@ -461,7 +471,7 @@ func TestChromium_Routes(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
scenario: "routes not disabled",
|
scenario: "routes not disabled",
|
||||||
expectRoutes: 6,
|
expectRoutes: 3,
|
||||||
disableRoutes: false,
|
disableRoutes: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -495,14 +505,14 @@ func TestChromium_Pdf(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
scenario: "PDF task success",
|
scenario: "PDF task success",
|
||||||
browser: &browserMock{pdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error {
|
browser: &browserMock{pdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options Options) error {
|
||||||
return nil
|
return nil
|
||||||
}},
|
}},
|
||||||
expectError: false,
|
expectError: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "PDF task error",
|
scenario: "PDF task error",
|
||||||
browser: &browserMock{pdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error {
|
browser: &browserMock{pdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options Options) error {
|
||||||
return errors.New("PDF task error")
|
return errors.New("PDF task error")
|
||||||
}},
|
}},
|
||||||
expectError: true,
|
expectError: true,
|
||||||
@@ -515,49 +525,7 @@ func TestChromium_Pdf(t *testing.T) {
|
|||||||
}}
|
}}
|
||||||
mod.browser = tc.browser
|
mod.browser = tc.browser
|
||||||
|
|
||||||
err := mod.Pdf(context.Background(), zap.NewNop(), "", "", PdfOptions{})
|
err := mod.Pdf(context.Background(), zap.NewNop(), "", "", Options{})
|
||||||
|
|
||||||
if !tc.expectError && err != nil {
|
|
||||||
t.Fatalf("expected no error but got: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if tc.expectError && err == nil {
|
|
||||||
t.Fatal("expected error but got none")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestChromium_Screenshot(t *testing.T) {
|
|
||||||
for _, tc := range []struct {
|
|
||||||
scenario string
|
|
||||||
supervisor gotenberg.ProcessSupervisor
|
|
||||||
browser browser
|
|
||||||
expectError bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
scenario: "Screenshot task success",
|
|
||||||
browser: &browserMock{screenshotMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error {
|
|
||||||
return nil
|
|
||||||
}},
|
|
||||||
expectError: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
scenario: "Screenshot task error",
|
|
||||||
browser: &browserMock{screenshotMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error {
|
|
||||||
return errors.New("screenshot task error")
|
|
||||||
}},
|
|
||||||
expectError: true,
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
t.Run(tc.scenario, func(t *testing.T) {
|
|
||||||
mod := new(Chromium)
|
|
||||||
mod.supervisor = &gotenberg.ProcessSupervisorMock{RunMock: func(ctx context.Context, logger *zap.Logger, task func() error) error {
|
|
||||||
return task()
|
|
||||||
}}
|
|
||||||
mod.browser = tc.browser
|
|
||||||
|
|
||||||
err := mod.Screenshot(context.Background(), zap.NewNop(), "", "", ScreenshotOptions{})
|
|
||||||
|
|
||||||
if !tc.expectError && err != nil {
|
if !tc.expectError && err != nil {
|
||||||
t.Fatalf("expected no error but got: %v", err)
|
t.Fatalf("expected no error but got: %v", err)
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"slices"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/chromedp/cdproto/cdp"
|
"github.com/chromedp/cdproto/cdp"
|
||||||
@@ -61,37 +60,6 @@ func listenForEventRequestPaused(ctx context.Context, logger *zap.Logger, allowL
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// listenForEventResponseReceived listens for an invalid HTTP status code is
|
|
||||||
// returned by the main page.
|
|
||||||
// See https://github.com/gotenberg/gotenberg/issues/613.
|
|
||||||
func listenForEventResponseReceived(ctx context.Context, logger *zap.Logger, url string, failOnHttpStatusCodes []int64, invalidHttpStatusCode *error, invalidHttpStatusCodeMu *sync.RWMutex) {
|
|
||||||
for _, code := range []int64{199, 299, 399, 499, 599} {
|
|
||||||
if slices.Contains(failOnHttpStatusCodes, code) {
|
|
||||||
for i := code - 99; i <= code; i++ {
|
|
||||||
failOnHttpStatusCodes = append(failOnHttpStatusCodes, i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
chromedp.ListenTarget(ctx, func(ev interface{}) {
|
|
||||||
switch ev := ev.(type) {
|
|
||||||
case *network.EventResponseReceived:
|
|
||||||
if ev.Response.URL != url {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Debug(fmt.Sprintf("event EventResponseReceived fired for main page: %+v", ev.Response))
|
|
||||||
|
|
||||||
if slices.Contains(failOnHttpStatusCodes, ev.Response.Status) {
|
|
||||||
invalidHttpStatusCodeMu.Lock()
|
|
||||||
defer invalidHttpStatusCodeMu.Unlock()
|
|
||||||
|
|
||||||
*invalidHttpStatusCode = fmt.Errorf("%d: %s", ev.Response.Status, ev.Response.StatusText)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// listenForEventExceptionThrown listens for exceptions in the console and
|
// listenForEventExceptionThrown listens for exceptions in the console and
|
||||||
// appends those exceptions to the given error pointer.
|
// appends those exceptions to the given error pointer.
|
||||||
// See https://github.com/gotenberg/gotenberg/issues/262.
|
// See https://github.com/gotenberg/gotenberg/issues/262.
|
||||||
|
|||||||
@@ -5,38 +5,28 @@ import (
|
|||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ApiMock is a mock for the [Api] interface.
|
// ApiMock is a mock for the [Api] interface.
|
||||||
type ApiMock struct {
|
type ApiMock struct {
|
||||||
PdfMock func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error
|
PdfMock func(ctx context.Context, logger *zap.Logger, url, outputPath string, options Options) error
|
||||||
ScreenshotMock func(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *ApiMock) Pdf(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error {
|
func (api *ApiMock) Pdf(ctx context.Context, logger *zap.Logger, url, outputPath string, options Options) error {
|
||||||
return api.PdfMock(ctx, logger, url, outputPath, options)
|
return api.PdfMock(ctx, logger, url, outputPath, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *ApiMock) Screenshot(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error {
|
|
||||||
return api.ScreenshotMock(ctx, logger, url, outputPath, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
// browserMock is a mock for the [browser] interface.
|
// browserMock is a mock for the [browser] interface.
|
||||||
type browserMock struct {
|
type browserMock struct {
|
||||||
gotenberg.ProcessMock
|
gotenberg.ProcessMock
|
||||||
pdfMock func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error
|
pdfMock func(ctx context.Context, logger *zap.Logger, url, outputPath string, options Options) error
|
||||||
screenshotMock func(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *browserMock) pdf(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error {
|
func (b *browserMock) pdf(ctx context.Context, logger *zap.Logger, url, outputPath string, options Options) error {
|
||||||
return b.pdfMock(ctx, logger, url, outputPath, options)
|
return b.pdfMock(ctx, logger, url, outputPath, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *browserMock) screenshot(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error {
|
|
||||||
return b.screenshotMock(ctx, logger, url, outputPath, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interface guards.
|
// Interface guards.
|
||||||
var (
|
var (
|
||||||
_ Api = (*ApiMock)(nil)
|
_ Api = (*ApiMock)(nil)
|
||||||
|
|||||||
@@ -9,42 +9,26 @@ import (
|
|||||||
|
|
||||||
func TestApiMock(t *testing.T) {
|
func TestApiMock(t *testing.T) {
|
||||||
mock := &ApiMock{
|
mock := &ApiMock{
|
||||||
PdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error {
|
PdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options Options) error {
|
||||||
return nil
|
|
||||||
},
|
|
||||||
ScreenshotMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error {
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
err := mock.Pdf(context.Background(), zap.NewNop(), "", "", PdfOptions{})
|
err := mock.Pdf(context.Background(), zap.NewNop(), "", "", Options{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("expected no error from ApiMock.Pdf, but got: %v", err)
|
t.Errorf("expected no error from ApiMock.Pdf, but got: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = mock.Screenshot(context.Background(), zap.NewNop(), "", "", ScreenshotOptions{})
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("expected no error from ApiMock.Screenshot, but got: %v", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBrowserMock(t *testing.T) {
|
func TestBrowserMock(t *testing.T) {
|
||||||
mock := &browserMock{
|
mock := &browserMock{
|
||||||
pdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error {
|
pdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options Options) error {
|
||||||
return nil
|
|
||||||
},
|
|
||||||
screenshotMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error {
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
err := mock.pdf(context.Background(), zap.NewNop(), "", "", PdfOptions{})
|
err := mock.pdf(context.Background(), zap.NewNop(), "", "", Options{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("expected no error from browserMock.pdf, but got: %v", err)
|
t.Errorf("expected no error from browserMock.pdf, but got: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = mock.screenshot(context.Background(), zap.NewNop(), "", "", ScreenshotOptions{})
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("expected no error from browserMock.screenshot, but got: %v", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -18,55 +17,47 @@ import (
|
|||||||
"github.com/russross/blackfriday/v2"
|
"github.com/russross/blackfriday/v2"
|
||||||
"go.uber.org/multierr"
|
"go.uber.org/multierr"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/modules/api"
|
"github.com/gotenberg/gotenberg/v7/pkg/modules/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FormDataChromiumOptions creates [Options] from the form data. Fallback to
|
// FormDataChromiumPdfOptions creates [Options] from the form data. Fallback to
|
||||||
// default value if the considered key is not present.
|
// default value if the considered key is not present.
|
||||||
func FormDataChromiumOptions(ctx *api.Context) (*api.FormData, Options) {
|
func FormDataChromiumPdfOptions(ctx *api.Context) (*api.FormData, Options) {
|
||||||
defaultOptions := DefaultOptions()
|
defaultOptions := DefaultOptions()
|
||||||
|
|
||||||
var (
|
var (
|
||||||
skipNetworkIdleEvent bool
|
failOnConsoleExceptions bool
|
||||||
failOnHttpStatusCodes []int64
|
waitDelay time.Duration
|
||||||
failOnConsoleExceptions bool
|
waitWindowStatus string
|
||||||
waitDelay time.Duration
|
waitForExpression string
|
||||||
waitWindowStatus string
|
userAgent string
|
||||||
waitForExpression string
|
extraHttpHeaders map[string]string
|
||||||
extraHttpHeaders map[string]string
|
emulatedMediaType string
|
||||||
emulatedMediaType string
|
landscape, printBackground, omitBackground bool
|
||||||
omitBackground bool
|
scale, paperWidth, paperHeight float64
|
||||||
|
marginTop, marginBottom, marginLeft, marginRight float64
|
||||||
|
pageRanges string
|
||||||
|
headerTemplate, footerTemplate string
|
||||||
|
preferCssPageSize bool
|
||||||
)
|
)
|
||||||
|
|
||||||
form := ctx.FormData().
|
form := ctx.FormData().
|
||||||
Bool("skipNetworkIdleEvent", &skipNetworkIdleEvent, defaultOptions.SkipNetworkIdleEvent).
|
|
||||||
Custom("failOnHttpStatusCodes", func(value string) error {
|
|
||||||
if value == "" {
|
|
||||||
failOnHttpStatusCodes = defaultOptions.FailOnHttpStatusCodes
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err := json.Unmarshal([]byte(value), &failOnHttpStatusCodes)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unmarshal failOnHttpStatusCodes: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}).
|
|
||||||
Bool("failOnConsoleExceptions", &failOnConsoleExceptions, defaultOptions.FailOnConsoleExceptions).
|
Bool("failOnConsoleExceptions", &failOnConsoleExceptions, defaultOptions.FailOnConsoleExceptions).
|
||||||
Duration("waitDelay", &waitDelay, defaultOptions.WaitDelay).
|
Duration("waitDelay", &waitDelay, defaultOptions.WaitDelay).
|
||||||
String("waitWindowStatus", &waitWindowStatus, defaultOptions.WaitWindowStatus).
|
String("waitWindowStatus", &waitWindowStatus, defaultOptions.WaitWindowStatus).
|
||||||
String("waitForExpression", &waitForExpression, defaultOptions.WaitForExpression).
|
String("waitForExpression", &waitForExpression, defaultOptions.WaitForExpression).
|
||||||
|
String("userAgent", &userAgent, ""). // FIXME: deprecated.
|
||||||
Custom("extraHttpHeaders", func(value string) error {
|
Custom("extraHttpHeaders", func(value string) error {
|
||||||
if value == "" {
|
if value == "" {
|
||||||
extraHttpHeaders = defaultOptions.ExtraHttpHeaders
|
extraHttpHeaders = defaultOptions.ExtraHttpHeaders
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err := json.Unmarshal([]byte(value), &extraHttpHeaders)
|
err := json.Unmarshal([]byte(value), &extraHttpHeaders)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unmarshal extraHttpHeaders: %w", err)
|
return fmt.Errorf("unmarshal extra HTTP headers: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -74,6 +65,7 @@ func FormDataChromiumOptions(ctx *api.Context) (*api.FormData, Options) {
|
|||||||
Custom("emulatedMediaType", func(value string) error {
|
Custom("emulatedMediaType", func(value string) error {
|
||||||
if value == "" {
|
if value == "" {
|
||||||
emulatedMediaType = defaultOptions.EmulatedMediaType
|
emulatedMediaType = defaultOptions.EmulatedMediaType
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,148 +77,87 @@ func FormDataChromiumOptions(ctx *api.Context) (*api.FormData, Options) {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}).
|
}).
|
||||||
Bool("omitBackground", &omitBackground, defaultOptions.OmitBackground)
|
Bool("landscape", &landscape, defaultOptions.Landscape).
|
||||||
|
Bool("printBackground", &printBackground, defaultOptions.PrintBackground).
|
||||||
|
Bool("omitBackground", &omitBackground, defaultOptions.OmitBackground).
|
||||||
|
Float64("scale", &scale, defaultOptions.Scale).
|
||||||
|
Float64("paperWidth", &paperWidth, defaultOptions.PaperWidth).
|
||||||
|
Float64("paperHeight", &paperHeight, defaultOptions.PaperHeight).
|
||||||
|
Float64("marginTop", &marginTop, defaultOptions.MarginTop).
|
||||||
|
Float64("marginBottom", &marginBottom, defaultOptions.MarginBottom).
|
||||||
|
Float64("marginLeft", &marginLeft, defaultOptions.MarginLeft).
|
||||||
|
Float64("marginRight", &marginRight, defaultOptions.MarginRight).
|
||||||
|
String("nativePageRanges", &pageRanges, defaultOptions.PageRanges).
|
||||||
|
Content("header.html", &headerTemplate, defaultOptions.HeaderTemplate).
|
||||||
|
Content("footer.html", &footerTemplate, defaultOptions.FooterTemplate).
|
||||||
|
Bool("preferCssPageSize", &preferCssPageSize, defaultOptions.PreferCssPageSize)
|
||||||
|
|
||||||
|
// FIXME: deprecated.
|
||||||
|
if userAgent != "" {
|
||||||
|
ctx.Log().Warn("'userAgent' is deprecated; prefer the 'extraHttpHeaders' form field instead")
|
||||||
|
|
||||||
|
if extraHttpHeaders == nil {
|
||||||
|
extraHttpHeaders = make(map[string]string)
|
||||||
|
}
|
||||||
|
|
||||||
|
extraHttpHeaders["User-Agent"] = userAgent
|
||||||
|
}
|
||||||
|
|
||||||
options := Options{
|
options := Options{
|
||||||
SkipNetworkIdleEvent: skipNetworkIdleEvent,
|
|
||||||
FailOnHttpStatusCodes: failOnHttpStatusCodes,
|
|
||||||
FailOnConsoleExceptions: failOnConsoleExceptions,
|
FailOnConsoleExceptions: failOnConsoleExceptions,
|
||||||
WaitDelay: waitDelay,
|
WaitDelay: waitDelay,
|
||||||
WaitWindowStatus: waitWindowStatus,
|
WaitWindowStatus: waitWindowStatus,
|
||||||
WaitForExpression: waitForExpression,
|
WaitForExpression: waitForExpression,
|
||||||
ExtraHttpHeaders: extraHttpHeaders,
|
ExtraHttpHeaders: extraHttpHeaders,
|
||||||
EmulatedMediaType: emulatedMediaType,
|
EmulatedMediaType: emulatedMediaType,
|
||||||
|
Landscape: landscape,
|
||||||
|
PrintBackground: printBackground,
|
||||||
OmitBackground: omitBackground,
|
OmitBackground: omitBackground,
|
||||||
|
Scale: scale,
|
||||||
|
PaperWidth: paperWidth,
|
||||||
|
PaperHeight: paperHeight,
|
||||||
|
MarginTop: marginTop,
|
||||||
|
MarginBottom: marginBottom,
|
||||||
|
MarginLeft: marginLeft,
|
||||||
|
MarginRight: marginRight,
|
||||||
|
PageRanges: pageRanges,
|
||||||
|
HeaderTemplate: headerTemplate,
|
||||||
|
FooterTemplate: footerTemplate,
|
||||||
|
PreferCssPageSize: preferCssPageSize,
|
||||||
}
|
}
|
||||||
|
|
||||||
return form, options
|
return form, options
|
||||||
}
|
}
|
||||||
|
|
||||||
// FormDataChromiumPdfOptions creates [PdfOptions] from the form data. Fallback to
|
|
||||||
// default value if the considered key is not present.
|
|
||||||
func FormDataChromiumPdfOptions(ctx *api.Context) (*api.FormData, PdfOptions) {
|
|
||||||
form, options := FormDataChromiumOptions(ctx)
|
|
||||||
defaultPdfOptions := DefaultPdfOptions()
|
|
||||||
|
|
||||||
var (
|
|
||||||
landscape, printBackground bool
|
|
||||||
scale, paperWidth, paperHeight float64
|
|
||||||
marginTop, marginBottom, marginLeft, marginRight float64
|
|
||||||
pageRanges string
|
|
||||||
headerTemplate, footerTemplate string
|
|
||||||
preferCssPageSize bool
|
|
||||||
)
|
|
||||||
|
|
||||||
form.
|
|
||||||
Bool("landscape", &landscape, defaultPdfOptions.Landscape).
|
|
||||||
Bool("printBackground", &printBackground, defaultPdfOptions.PrintBackground).
|
|
||||||
Float64("scale", &scale, defaultPdfOptions.Scale).
|
|
||||||
Float64("paperWidth", &paperWidth, defaultPdfOptions.PaperWidth).
|
|
||||||
Float64("paperHeight", &paperHeight, defaultPdfOptions.PaperHeight).
|
|
||||||
Float64("marginTop", &marginTop, defaultPdfOptions.MarginTop).
|
|
||||||
Float64("marginBottom", &marginBottom, defaultPdfOptions.MarginBottom).
|
|
||||||
Float64("marginLeft", &marginLeft, defaultPdfOptions.MarginLeft).
|
|
||||||
Float64("marginRight", &marginRight, defaultPdfOptions.MarginRight).
|
|
||||||
String("nativePageRanges", &pageRanges, defaultPdfOptions.PageRanges).
|
|
||||||
Content("header.html", &headerTemplate, defaultPdfOptions.HeaderTemplate).
|
|
||||||
Content("footer.html", &footerTemplate, defaultPdfOptions.FooterTemplate).
|
|
||||||
Bool("preferCssPageSize", &preferCssPageSize, defaultPdfOptions.PreferCssPageSize)
|
|
||||||
|
|
||||||
pdfOptions := PdfOptions{
|
|
||||||
Options: options,
|
|
||||||
Landscape: landscape,
|
|
||||||
PrintBackground: printBackground,
|
|
||||||
Scale: scale,
|
|
||||||
PaperWidth: paperWidth,
|
|
||||||
PaperHeight: paperHeight,
|
|
||||||
MarginTop: marginTop,
|
|
||||||
MarginBottom: marginBottom,
|
|
||||||
MarginLeft: marginLeft,
|
|
||||||
MarginRight: marginRight,
|
|
||||||
PageRanges: pageRanges,
|
|
||||||
HeaderTemplate: headerTemplate,
|
|
||||||
FooterTemplate: footerTemplate,
|
|
||||||
PreferCssPageSize: preferCssPageSize,
|
|
||||||
}
|
|
||||||
|
|
||||||
return form, pdfOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
// FormDataChromiumScreenshotOptions creates [ScreenshotOptions] from the form
|
|
||||||
// data. Fallback to default value if the considered key is not present.
|
|
||||||
func FormDataChromiumScreenshotOptions(ctx *api.Context) (*api.FormData, ScreenshotOptions) {
|
|
||||||
form, options := FormDataChromiumOptions(ctx)
|
|
||||||
defaultScreenshotOptions := DefaultScreenshotOptions()
|
|
||||||
|
|
||||||
var (
|
|
||||||
format string
|
|
||||||
quality int
|
|
||||||
optimizeForSpeed bool
|
|
||||||
)
|
|
||||||
|
|
||||||
form.
|
|
||||||
Custom("format", func(value string) error {
|
|
||||||
if value == "" {
|
|
||||||
format = defaultScreenshotOptions.Format
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if value != "png" && value != "jpeg" && value != "webp" {
|
|
||||||
return fmt.Errorf("wrong value, expected either 'png', 'jpeg' or 'webp'")
|
|
||||||
}
|
|
||||||
|
|
||||||
format = value
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}).
|
|
||||||
Custom("quality", func(value string) error {
|
|
||||||
if value == "" {
|
|
||||||
quality = defaultScreenshotOptions.Quality
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
intValue, err := strconv.Atoi(value)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if intValue < 0 {
|
|
||||||
return errors.New("value is negative")
|
|
||||||
}
|
|
||||||
|
|
||||||
if intValue > 100 {
|
|
||||||
return errors.New("value is superior to 100")
|
|
||||||
}
|
|
||||||
|
|
||||||
quality = intValue
|
|
||||||
return nil
|
|
||||||
}).
|
|
||||||
Bool("optimizeForSpeed", &optimizeForSpeed, defaultScreenshotOptions.OptimizeForSpeed)
|
|
||||||
|
|
||||||
screenshotOptions := ScreenshotOptions{
|
|
||||||
Options: options,
|
|
||||||
Format: format,
|
|
||||||
Quality: quality,
|
|
||||||
OptimizeForSpeed: optimizeForSpeed,
|
|
||||||
}
|
|
||||||
|
|
||||||
return form, screenshotOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
// FormDataChromiumPdfFormats creates [gotenberg.PdfFormats] from the form
|
// FormDataChromiumPdfFormats creates [gotenberg.PdfFormats] from the form
|
||||||
// data. Fallback to default value if the considered key is not present.
|
// data. Fallback to default value if the considered key is not present.
|
||||||
func FormDataChromiumPdfFormats(form *api.FormData) gotenberg.PdfFormats {
|
func FormDataChromiumPdfFormats(ctx *api.Context) gotenberg.PdfFormats {
|
||||||
var (
|
var (
|
||||||
pdfa string
|
pdfFormat string
|
||||||
pdfua bool
|
pdfa string
|
||||||
|
pdfua bool
|
||||||
)
|
)
|
||||||
|
|
||||||
form.
|
ctx.FormData().
|
||||||
|
String("pdfFormat", &pdfFormat, "").
|
||||||
String("pdfa", &pdfa, "").
|
String("pdfa", &pdfa, "").
|
||||||
Bool("pdfua", &pdfua, false)
|
Bool("pdfua", &pdfua, false)
|
||||||
|
|
||||||
|
// FIXME: deprecated.
|
||||||
|
// pdfa > pdfFormat.
|
||||||
|
var actualPdfArchive string
|
||||||
|
|
||||||
|
if pdfFormat != "" {
|
||||||
|
ctx.Log().Warn("'pdfFormat' is deprecated; prefer the 'pdfa' form field instead")
|
||||||
|
actualPdfArchive = pdfFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
if pdfa != "" {
|
||||||
|
actualPdfArchive = pdfa
|
||||||
|
}
|
||||||
|
|
||||||
return gotenberg.PdfFormats{
|
return gotenberg.PdfFormats{
|
||||||
PdfA: pdfa,
|
PdfA: actualPdfArchive,
|
||||||
PdfUa: pdfua,
|
PdfUa: pdfua,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -240,7 +171,7 @@ func convertUrlRoute(chromium Api, engine gotenberg.PdfEngine) api.Route {
|
|||||||
Handler: func(c echo.Context) error {
|
Handler: func(c echo.Context) error {
|
||||||
ctx := c.Get("context").(*api.Context)
|
ctx := c.Get("context").(*api.Context)
|
||||||
form, options := FormDataChromiumPdfOptions(ctx)
|
form, options := FormDataChromiumPdfOptions(ctx)
|
||||||
pdfFormats := FormDataChromiumPdfFormats(form)
|
pdfFormats := FormDataChromiumPdfFormats(ctx)
|
||||||
|
|
||||||
var url string
|
var url string
|
||||||
err := form.
|
err := form.
|
||||||
@@ -260,35 +191,6 @@ func convertUrlRoute(chromium Api, engine gotenberg.PdfEngine) api.Route {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// screenshotUrlRoute returns an [api.Route] which can take a screenshot from a
|
|
||||||
// URL.
|
|
||||||
func screenshotUrlRoute(chromium Api) api.Route {
|
|
||||||
return api.Route{
|
|
||||||
Method: http.MethodPost,
|
|
||||||
Path: "/forms/chromium/screenshot/url",
|
|
||||||
IsMultipart: true,
|
|
||||||
Handler: func(c echo.Context) error {
|
|
||||||
ctx := c.Get("context").(*api.Context)
|
|
||||||
form, options := FormDataChromiumScreenshotOptions(ctx)
|
|
||||||
|
|
||||||
var url string
|
|
||||||
err := form.
|
|
||||||
MandatoryString("url", &url).
|
|
||||||
Validate()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("validate form data: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = screenshotUrl(ctx, chromium, url, options)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("URL screenshot: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// convertHtmlRoute returns an [api.Route] which can convert an HTML file to
|
// convertHtmlRoute returns an [api.Route] which can convert an HTML file to
|
||||||
// PDF.
|
// PDF.
|
||||||
func convertHtmlRoute(chromium Api, engine gotenberg.PdfEngine) api.Route {
|
func convertHtmlRoute(chromium Api, engine gotenberg.PdfEngine) api.Route {
|
||||||
@@ -299,7 +201,7 @@ func convertHtmlRoute(chromium Api, engine gotenberg.PdfEngine) api.Route {
|
|||||||
Handler: func(c echo.Context) error {
|
Handler: func(c echo.Context) error {
|
||||||
ctx := c.Get("context").(*api.Context)
|
ctx := c.Get("context").(*api.Context)
|
||||||
form, options := FormDataChromiumPdfOptions(ctx)
|
form, options := FormDataChromiumPdfOptions(ctx)
|
||||||
pdfFormats := FormDataChromiumPdfFormats(form)
|
pdfFormats := FormDataChromiumPdfFormats(ctx)
|
||||||
|
|
||||||
var inputPath string
|
var inputPath string
|
||||||
err := form.
|
err := form.
|
||||||
@@ -320,36 +222,6 @@ func convertHtmlRoute(chromium Api, engine gotenberg.PdfEngine) api.Route {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// screenshotHtmlRoute returns an [api.Route] which can take a screenshot from
|
|
||||||
// an HTML file.
|
|
||||||
func screenshotHtmlRoute(chromium Api) api.Route {
|
|
||||||
return api.Route{
|
|
||||||
Method: http.MethodPost,
|
|
||||||
Path: "/forms/chromium/screenshot/html",
|
|
||||||
IsMultipart: true,
|
|
||||||
Handler: func(c echo.Context) error {
|
|
||||||
ctx := c.Get("context").(*api.Context)
|
|
||||||
form, options := FormDataChromiumScreenshotOptions(ctx)
|
|
||||||
|
|
||||||
var inputPath string
|
|
||||||
err := form.
|
|
||||||
MandatoryPath("index.html", &inputPath).
|
|
||||||
Validate()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("validate form data: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
url := fmt.Sprintf("file://%s", inputPath)
|
|
||||||
err = screenshotUrl(ctx, chromium, url, options)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("HTML screenshot: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// convertMarkdownRoute returns an [api.Route] which can convert markdown files
|
// convertMarkdownRoute returns an [api.Route] which can convert markdown files
|
||||||
// to PDF.
|
// to PDF.
|
||||||
func convertMarkdownRoute(chromium Api, engine gotenberg.PdfEngine) api.Route {
|
func convertMarkdownRoute(chromium Api, engine gotenberg.PdfEngine) api.Route {
|
||||||
@@ -360,7 +232,7 @@ func convertMarkdownRoute(chromium Api, engine gotenberg.PdfEngine) api.Route {
|
|||||||
Handler: func(c echo.Context) error {
|
Handler: func(c echo.Context) error {
|
||||||
ctx := c.Get("context").(*api.Context)
|
ctx := c.Get("context").(*api.Context)
|
||||||
form, options := FormDataChromiumPdfOptions(ctx)
|
form, options := FormDataChromiumPdfOptions(ctx)
|
||||||
pdfFormats := FormDataChromiumPdfFormats(form)
|
pdfFormats := FormDataChromiumPdfFormats(ctx)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
inputPath string
|
inputPath string
|
||||||
@@ -375,11 +247,79 @@ func convertMarkdownRoute(chromium Api, engine gotenberg.PdfEngine) api.Route {
|
|||||||
return fmt.Errorf("validate form data: %w", err)
|
return fmt.Errorf("validate form data: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
url, err := markdownToHtml(ctx, inputPath, markdownPaths)
|
// We have to convert each markdown file referenced in the HTML
|
||||||
|
// file to... HTML. Thanks to the "html/template" package, we are
|
||||||
|
// able to provide the "toHTML" function which the user may call
|
||||||
|
// directly inside the HTML file.
|
||||||
|
|
||||||
|
var markdownFilesNotFoundErr error
|
||||||
|
|
||||||
|
tmpl, err := template.
|
||||||
|
New(filepath.Base(inputPath)).
|
||||||
|
Funcs(template.FuncMap{
|
||||||
|
"toHTML": func(filename string) (template.HTML, error) {
|
||||||
|
var path string
|
||||||
|
|
||||||
|
for _, markdownPath := range markdownPaths {
|
||||||
|
markdownFilename := filepath.Base(markdownPath)
|
||||||
|
|
||||||
|
if filename == markdownFilename {
|
||||||
|
path = markdownPath
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if path == "" {
|
||||||
|
markdownFilesNotFoundErr = multierr.Append(
|
||||||
|
markdownFilesNotFoundErr,
|
||||||
|
fmt.Errorf("'%s'", filename),
|
||||||
|
)
|
||||||
|
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("read markdown file '%s': %w", filename, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe := blackfriday.Run(b)
|
||||||
|
sanitized := bluemonday.UGCPolicy().SanitizeBytes(unsafe)
|
||||||
|
|
||||||
|
// #nosec
|
||||||
|
return template.HTML(sanitized), nil
|
||||||
|
},
|
||||||
|
}).ParseFiles(inputPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("transform markdown file(s) to HTML: %w", err)
|
return fmt.Errorf("parse template file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
|
||||||
|
err = tmpl.Execute(&buffer, &struct{}{})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("execute template: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if markdownFilesNotFoundErr != nil {
|
||||||
|
return api.WrapError(
|
||||||
|
fmt.Errorf("markdown files not found: %w", markdownFilesNotFoundErr),
|
||||||
|
api.NewSentinelHttpError(
|
||||||
|
http.StatusBadRequest,
|
||||||
|
fmt.Sprintf("Markdown file(s) not found: %s", markdownFilesNotFoundErr),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
inputPath = ctx.GeneratePath(".html")
|
||||||
|
|
||||||
|
err = os.WriteFile(inputPath, buffer.Bytes(), 0o600)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("write template result: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
url := fmt.Sprintf("file://%s", inputPath)
|
||||||
|
|
||||||
err = convertUrl(ctx, chromium, engine, url, pdfFormats, options)
|
err = convertUrl(ctx, chromium, engine, url, pdfFormats, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("convert markdown to PDF: %w", err)
|
return fmt.Errorf("convert markdown to PDF: %w", err)
|
||||||
@@ -390,129 +330,26 @@ func convertMarkdownRoute(chromium Api, engine gotenberg.PdfEngine) api.Route {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// screenshotMarkdownRoute returns an [api.Route] which can take a screenshot
|
// convertUrl is a stub which is called by the other methods of this file.
|
||||||
// from markdown files.
|
func convertUrl(ctx *api.Context, chromium Api, engine gotenberg.PdfEngine, url string, pdfFormats gotenberg.PdfFormats, options Options) error {
|
||||||
func screenshotMarkdownRoute(chromium Api) api.Route {
|
|
||||||
return api.Route{
|
|
||||||
Method: http.MethodPost,
|
|
||||||
Path: "/forms/chromium/screenshot/markdown",
|
|
||||||
IsMultipart: true,
|
|
||||||
Handler: func(c echo.Context) error {
|
|
||||||
ctx := c.Get("context").(*api.Context)
|
|
||||||
form, options := FormDataChromiumScreenshotOptions(ctx)
|
|
||||||
|
|
||||||
var (
|
|
||||||
inputPath string
|
|
||||||
markdownPaths []string
|
|
||||||
)
|
|
||||||
|
|
||||||
err := form.
|
|
||||||
MandatoryPath("index.html", &inputPath).
|
|
||||||
MandatoryPaths([]string{".md"}, &markdownPaths).
|
|
||||||
Validate()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("validate form data: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
url, err := markdownToHtml(ctx, inputPath, markdownPaths)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("transform markdown file(s) to HTML: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = screenshotUrl(ctx, chromium, url, options)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("markdown screenshot: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func markdownToHtml(ctx *api.Context, inputPath string, markdownPaths []string) (string, error) {
|
|
||||||
// We have to convert each markdown file referenced in the HTML
|
|
||||||
// file to... HTML. Thanks to the "html/template" package, we are
|
|
||||||
// able to provide the "toHTML" function which the user may call
|
|
||||||
// directly inside the HTML file.
|
|
||||||
|
|
||||||
var markdownFilesNotFoundErr error
|
|
||||||
|
|
||||||
tmpl, err := template.
|
|
||||||
New(filepath.Base(inputPath)).
|
|
||||||
Funcs(template.FuncMap{
|
|
||||||
"toHTML": func(filename string) (template.HTML, error) {
|
|
||||||
var path string
|
|
||||||
|
|
||||||
for _, markdownPath := range markdownPaths {
|
|
||||||
markdownFilename := filepath.Base(markdownPath)
|
|
||||||
|
|
||||||
if filename == markdownFilename {
|
|
||||||
path = markdownPath
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if path == "" {
|
|
||||||
markdownFilesNotFoundErr = multierr.Append(
|
|
||||||
markdownFilesNotFoundErr,
|
|
||||||
fmt.Errorf("'%s'", filename),
|
|
||||||
)
|
|
||||||
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
b, err := os.ReadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("read markdown file '%s': %w", filename, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe := blackfriday.Run(b)
|
|
||||||
sanitized := bluemonday.UGCPolicy().SanitizeBytes(unsafe)
|
|
||||||
|
|
||||||
// #nosec
|
|
||||||
return template.HTML(sanitized), nil
|
|
||||||
},
|
|
||||||
}).ParseFiles(inputPath)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("parse template file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var buffer bytes.Buffer
|
|
||||||
|
|
||||||
err = tmpl.Execute(&buffer, &struct{}{})
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("execute template: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if markdownFilesNotFoundErr != nil {
|
|
||||||
return "", api.WrapError(
|
|
||||||
fmt.Errorf("markdown files not found: %w", markdownFilesNotFoundErr),
|
|
||||||
api.NewSentinelHttpError(
|
|
||||||
http.StatusBadRequest,
|
|
||||||
fmt.Sprintf("Markdown file(s) not found: %s", markdownFilesNotFoundErr),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
inputPath = ctx.GeneratePath(".html")
|
|
||||||
|
|
||||||
err = os.WriteFile(inputPath, buffer.Bytes(), 0o600)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("write template result: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("file://%s", inputPath), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertUrl(ctx *api.Context, chromium Api, engine gotenberg.PdfEngine, url string, pdfFormats gotenberg.PdfFormats, options PdfOptions) error {
|
|
||||||
outputPath := ctx.GeneratePath(".pdf")
|
outputPath := ctx.GeneratePath(".pdf")
|
||||||
|
|
||||||
err := chromium.Pdf(ctx, ctx.Log(), url, outputPath, options)
|
err := chromium.Pdf(ctx, ctx.Log(), url, outputPath, options)
|
||||||
err = handleChromiumError(err, url, options.Options)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
||||||
|
if errors.Is(err, ErrUrlNotAuthorized) {
|
||||||
|
return api.WrapError(
|
||||||
|
fmt.Errorf("convert to PDF: %w", err),
|
||||||
|
api.NewSentinelHttpError(
|
||||||
|
http.StatusForbidden,
|
||||||
|
fmt.Sprintf("'%s' does not match the authorized URLs", url),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if errors.Is(err, ErrOmitBackgroundWithoutPrintBackground) {
|
if errors.Is(err, ErrOmitBackgroundWithoutPrintBackground) {
|
||||||
return api.WrapError(
|
return api.WrapError(
|
||||||
err,
|
fmt.Errorf("convert to PDF: %w", err),
|
||||||
api.NewSentinelHttpError(
|
api.NewSentinelHttpError(
|
||||||
http.StatusBadRequest,
|
http.StatusBadRequest,
|
||||||
"omitBackground requires printBackground set to true",
|
"omitBackground requires printBackground set to true",
|
||||||
@@ -520,6 +357,23 @@ func convertUrl(ctx *api.Context, chromium Api, engine gotenberg.PdfEngine, url
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if errors.Is(err, ErrInvalidEvaluationExpression) {
|
||||||
|
if options.WaitForExpression == "" {
|
||||||
|
// We do not expect the 'waitWindowStatus' form field to return
|
||||||
|
// an ErrInvalidEvaluationExpression error. In such a scenario,
|
||||||
|
// we return a 500.
|
||||||
|
return fmt.Errorf("convert to PDF: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return api.WrapError(
|
||||||
|
fmt.Errorf("convert to PDF: %w", err),
|
||||||
|
api.NewSentinelHttpError(
|
||||||
|
http.StatusBadRequest,
|
||||||
|
fmt.Sprintf("The expression '%s' (waitForExpression) returned an exception or undefined", options.WaitForExpression),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if errors.Is(err, ErrInvalidPrinterSettings) {
|
if errors.Is(err, ErrInvalidPrinterSettings) {
|
||||||
return api.WrapError(
|
return api.WrapError(
|
||||||
fmt.Errorf("convert to PDF: %w", err),
|
fmt.Errorf("convert to PDF: %w", err),
|
||||||
@@ -540,6 +394,16 @@ func convertUrl(ctx *api.Context, chromium Api, engine gotenberg.PdfEngine, url
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if errors.Is(err, ErrConsoleExceptions) {
|
||||||
|
return api.WrapError(
|
||||||
|
fmt.Errorf("convert to PDF: %w", err),
|
||||||
|
api.NewSentinelHttpError(
|
||||||
|
http.StatusConflict,
|
||||||
|
fmt.Sprintf("Chromium console exceptions:\n %s", strings.ReplaceAll(err.Error(), ErrConsoleExceptions.Error(), "")),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return fmt.Errorf("convert to PDF: %w", err)
|
return fmt.Errorf("convert to PDF: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -578,76 +442,3 @@ func convertUrl(ctx *api.Context, chromium Api, engine gotenberg.PdfEngine, url
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func screenshotUrl(ctx *api.Context, chromium Api, url string, options ScreenshotOptions) error {
|
|
||||||
ext := fmt.Sprintf(".%s", options.Format)
|
|
||||||
outputPath := ctx.GeneratePath(ext)
|
|
||||||
|
|
||||||
err := chromium.Screenshot(ctx, ctx.Log(), url, outputPath, options)
|
|
||||||
err = handleChromiumError(err, url, options.Options)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("screenshot: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = ctx.AddOutputPaths(outputPath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("add output path: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleChromiumError(err error, url string, options Options) error {
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if errors.Is(err, ErrUrlNotAuthorized) {
|
|
||||||
return api.WrapError(
|
|
||||||
err,
|
|
||||||
api.NewSentinelHttpError(
|
|
||||||
http.StatusForbidden,
|
|
||||||
fmt.Sprintf("'%s' does not match the authorized URLs", url),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if errors.Is(err, ErrInvalidEvaluationExpression) {
|
|
||||||
if options.WaitForExpression == "" {
|
|
||||||
// We do not expect the 'waitWindowStatus' form field to return
|
|
||||||
// an ErrInvalidEvaluationExpression error. In such a scenario,
|
|
||||||
// we return a 500.
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return api.WrapError(
|
|
||||||
err,
|
|
||||||
api.NewSentinelHttpError(
|
|
||||||
http.StatusBadRequest,
|
|
||||||
fmt.Sprintf("The expression '%s' (waitForExpression) returned an exception or undefined", options.WaitForExpression),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if errors.Is(err, ErrInvalidHttpStatusCode) {
|
|
||||||
return api.WrapError(
|
|
||||||
err,
|
|
||||||
api.NewSentinelHttpError(
|
|
||||||
http.StatusConflict,
|
|
||||||
fmt.Sprintf("Invalid HTTP status code from the main page: %s", strings.ReplaceAll(err.Error(), fmt.Sprintf(": %s", ErrInvalidHttpStatusCode.Error()), "")),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if errors.Is(err, ErrConsoleExceptions) {
|
|
||||||
return api.WrapError(
|
|
||||||
err,
|
|
||||||
api.NewSentinelHttpError(
|
|
||||||
http.StatusConflict,
|
|
||||||
fmt.Sprintf("Chromium console exceptions:\n %s", strings.ReplaceAll(err.Error(), ErrConsoleExceptions.Error(), "")),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -15,7 +15,7 @@ import (
|
|||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
func printToPdfActionFunc(logger *zap.Logger, outputPath string, options PdfOptions) chromedp.ActionFunc {
|
func printToPdfActionFunc(logger *zap.Logger, outputPath string, options Options) chromedp.ActionFunc {
|
||||||
return func(ctx context.Context) error {
|
return func(ctx context.Context) error {
|
||||||
printToPdf := page.PrintToPDF().
|
printToPdf := page.PrintToPDF().
|
||||||
WithTransferMode(page.PrintToPDFTransferModeReturnAsStream).
|
WithTransferMode(page.PrintToPDFTransferModeReturnAsStream).
|
||||||
@@ -31,8 +31,8 @@ func printToPdfActionFunc(logger *zap.Logger, outputPath string, options PdfOpti
|
|||||||
WithPageRanges(options.PageRanges).
|
WithPageRanges(options.PageRanges).
|
||||||
WithPreferCSSPageSize(options.PreferCssPageSize)
|
WithPreferCSSPageSize(options.PreferCssPageSize)
|
||||||
|
|
||||||
hasCustomHeaderFooter := options.HeaderTemplate != DefaultPdfOptions().HeaderTemplate ||
|
hasCustomHeaderFooter := options.HeaderTemplate != DefaultOptions().HeaderTemplate ||
|
||||||
options.FooterTemplate != DefaultPdfOptions().FooterTemplate
|
options.FooterTemplate != DefaultOptions().FooterTemplate
|
||||||
|
|
||||||
if !hasCustomHeaderFooter {
|
if !hasCustomHeaderFooter {
|
||||||
logger.Debug("no custom header nor footer")
|
logger.Debug("no custom header nor footer")
|
||||||
@@ -63,7 +63,7 @@ func printToPdfActionFunc(logger *zap.Logger, outputPath string, options PdfOpti
|
|||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
err = reader.Close()
|
err := reader.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(fmt.Sprintf("close reader: %s", err))
|
logger.Error(fmt.Sprintf("close reader: %s", err))
|
||||||
}
|
}
|
||||||
@@ -75,7 +75,7 @@ func printToPdfActionFunc(logger *zap.Logger, outputPath string, options PdfOpti
|
|||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
err = file.Close()
|
err := file.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(fmt.Sprintf("close output path: %s", err))
|
logger.Error(fmt.Sprintf("close output path: %s", err))
|
||||||
}
|
}
|
||||||
@@ -92,90 +92,12 @@ func printToPdfActionFunc(logger *zap.Logger, outputPath string, options PdfOpti
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func captureScreenshotActionFunc(logger *zap.Logger, outputPath string, options ScreenshotOptions) chromedp.ActionFunc {
|
|
||||||
return func(ctx context.Context) error {
|
|
||||||
captureScreenshot := page.CaptureScreenshot().
|
|
||||||
WithCaptureBeyondViewport(true).
|
|
||||||
WithFromSurface(true).
|
|
||||||
WithOptimizeForSpeed(options.OptimizeForSpeed).
|
|
||||||
WithFormat(page.CaptureScreenshotFormat(options.Format))
|
|
||||||
|
|
||||||
if options.Format == "jpeg" {
|
|
||||||
captureScreenshot = captureScreenshot.
|
|
||||||
WithQuality(int64(options.Quality))
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Debug(fmt.Sprintf("capture screenshot with: %+v", captureScreenshot))
|
|
||||||
|
|
||||||
buffer, err := captureScreenshot.Do(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("capture screenshot: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
file, err := os.OpenFile(outputPath, os.O_CREATE|os.O_WRONLY, 0o600)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("open output path: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
err = file.Close()
|
|
||||||
if err != nil {
|
|
||||||
logger.Error(fmt.Sprintf("close output path: %s", err))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
_, err = file.Write(buffer)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("write result to output path: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func clearCacheActionFunc(logger *zap.Logger, clear bool) chromedp.ActionFunc {
|
|
||||||
return func(ctx context.Context) error {
|
|
||||||
// See https://github.com/gotenberg/gotenberg/issues/753.
|
|
||||||
if !clear {
|
|
||||||
logger.Debug("cache not cleared")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Debug("clear cache")
|
|
||||||
|
|
||||||
err := network.ClearBrowserCache().Do(ctx)
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("clear cache: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func clearCookiesActionFunc(logger *zap.Logger, clear bool) chromedp.ActionFunc {
|
|
||||||
return func(ctx context.Context) error {
|
|
||||||
// See https://github.com/gotenberg/gotenberg/issues/753.
|
|
||||||
if !clear {
|
|
||||||
logger.Debug("cookies not cleared")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Debug("clear cookies")
|
|
||||||
|
|
||||||
err := network.ClearBrowserCookies().Do(ctx)
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("clear cookies: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func disableJavaScriptActionFunc(logger *zap.Logger, disable bool) chromedp.ActionFunc {
|
func disableJavaScriptActionFunc(logger *zap.Logger, disable bool) chromedp.ActionFunc {
|
||||||
return func(ctx context.Context) error {
|
return func(ctx context.Context) error {
|
||||||
// See https://github.com/gotenberg/gotenberg/issues/175.
|
// See https://github.com/gotenberg/gotenberg/issues/175.
|
||||||
if !disable {
|
if !disable {
|
||||||
logger.Debug("JavaScript not disabled")
|
logger.Debug("JavaScript not disabled")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,6 +116,7 @@ func extraHttpHeadersActionFunc(logger *zap.Logger, extraHttpHeaders map[string]
|
|||||||
return func(ctx context.Context) error {
|
return func(ctx context.Context) error {
|
||||||
if len(extraHttpHeaders) == 0 {
|
if len(extraHttpHeaders) == 0 {
|
||||||
logger.Debug("no extra HTTP headers")
|
logger.Debug("no extra HTTP headers")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,7 +136,7 @@ func extraHttpHeadersActionFunc(logger *zap.Logger, extraHttpHeaders map[string]
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func navigateActionFunc(logger *zap.Logger, url string, skipNetworkIdleEvent bool) chromedp.ActionFunc {
|
func navigateActionFunc(logger *zap.Logger, url string) chromedp.ActionFunc {
|
||||||
return func(ctx context.Context) error {
|
return func(ctx context.Context) error {
|
||||||
logger.Debug(fmt.Sprintf("navigate to '%s'", url))
|
logger.Debug(fmt.Sprintf("navigate to '%s'", url))
|
||||||
|
|
||||||
@@ -222,21 +145,12 @@ func navigateActionFunc(logger *zap.Logger, url string, skipNetworkIdleEvent boo
|
|||||||
return fmt.Errorf("navigate to '%s': %w", url, err)
|
return fmt.Errorf("navigate to '%s': %w", url, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
waitFunc := []func() error{
|
|
||||||
waitForEventDomContentEventFired(ctx, logger),
|
|
||||||
waitForEventLoadEventFired(ctx, logger),
|
|
||||||
waitForEventLoadingFinished(ctx, logger),
|
|
||||||
}
|
|
||||||
|
|
||||||
if !skipNetworkIdleEvent {
|
|
||||||
waitFunc = append(waitFunc, waitForEventNetworkIdle(ctx, logger))
|
|
||||||
} else {
|
|
||||||
logger.Debug("skipping network idle event")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = runBatch(
|
err = runBatch(
|
||||||
ctx,
|
ctx,
|
||||||
waitFunc...,
|
waitForEventDomContentEventFired(ctx, logger),
|
||||||
|
waitForEventLoadEventFired(ctx, logger),
|
||||||
|
waitForEventNetworkIdle(ctx, logger),
|
||||||
|
waitForEventLoadingFinished(ctx, logger),
|
||||||
)
|
)
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -252,6 +166,7 @@ func hideDefaultWhiteBackgroundActionFunc(logger *zap.Logger, omitBackground, pr
|
|||||||
// See https://github.com/gotenberg/gotenberg/issues/226.
|
// See https://github.com/gotenberg/gotenberg/issues/226.
|
||||||
if !omitBackground {
|
if !omitBackground {
|
||||||
logger.Debug("default white background not hidden")
|
logger.Debug("default white background not hidden")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,6 +225,7 @@ func emulateMediaTypeActionFunc(logger *zap.Logger, mediaType string) chromedp.A
|
|||||||
return func(ctx context.Context) error {
|
return func(ctx context.Context) error {
|
||||||
if mediaType == "" {
|
if mediaType == "" {
|
||||||
logger.Debug("no emulated media type")
|
logger.Debug("no emulated media type")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -333,11 +249,13 @@ func waitDelayBeforePrintActionFunc(logger *zap.Logger, disableJavaScript bool,
|
|||||||
return func(ctx context.Context) error {
|
return func(ctx context.Context) error {
|
||||||
if disableJavaScript {
|
if disableJavaScript {
|
||||||
logger.Debug("JavaScript disabled, skipping wait delay")
|
logger.Debug("JavaScript disabled, skipping wait delay")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if delay <= 0 {
|
if delay <= 0 {
|
||||||
logger.Debug("no wait delay")
|
logger.Debug("no wait delay")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -358,11 +276,13 @@ func waitForExpressionBeforePrintActionFunc(logger *zap.Logger, disableJavaScrip
|
|||||||
return func(ctx context.Context) error {
|
return func(ctx context.Context) error {
|
||||||
if disableJavaScript {
|
if disableJavaScript {
|
||||||
logger.Debug("JavaScript disabled, skipping wait expression")
|
logger.Debug("JavaScript disabled, skipping wait expression")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if expression == "" {
|
if expression == "" {
|
||||||
logger.Debug("no wait expression")
|
logger.Debug("no wait expression")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ import (
|
|||||||
"go.uber.org/multierr"
|
"go.uber.org/multierr"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/modules/api"
|
"github.com/gotenberg/gotenberg/v7/pkg/modules/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -47,6 +47,7 @@ type Options struct {
|
|||||||
Landscape bool
|
Landscape bool
|
||||||
|
|
||||||
// PageRanges allows to select the pages to convert.
|
// PageRanges allows to select the pages to convert.
|
||||||
|
// TODO: should prefer a method form PdfEngine.
|
||||||
// Optional.
|
// Optional.
|
||||||
PageRanges string
|
PageRanges string
|
||||||
|
|
||||||
@@ -79,6 +80,21 @@ func (a *Api) Descriptor() gotenberg.ModuleDescriptor {
|
|||||||
ID: "libreoffice-api",
|
ID: "libreoffice-api",
|
||||||
FlagSet: func() *flag.FlagSet {
|
FlagSet: func() *flag.FlagSet {
|
||||||
fs := flag.NewFlagSet("api", flag.ExitOnError)
|
fs := flag.NewFlagSet("api", flag.ExitOnError)
|
||||||
|
|
||||||
|
// Deprecated flags.
|
||||||
|
fs.Duration("uno-listener-start-timeout", time.Duration(10)*time.Second, "Time limit for restarting the LibreOffice")
|
||||||
|
fs.Int64("uno-listener-restart-threshold", 10, "Conversions limit after which the LibreOffice listener is restarted - 0 means no restart")
|
||||||
|
fs.Bool("unoconv-disable-listener", false, "Do not start a long-running listener - save resources in detriment of unitary performance")
|
||||||
|
|
||||||
|
var err error
|
||||||
|
err = multierr.Append(err, fs.MarkDeprecated("uno-listener-start-timeout", "use the libreOffice-start-timeout property instead"))
|
||||||
|
err = multierr.Append(err, fs.MarkDeprecated("uno-listener-restart-threshold", "use the libreOffice-restart-after property instead"))
|
||||||
|
err = multierr.Append(err, fs.MarkDeprecated("unoconv-disable-listener", "use the libreOffice-auto-start property instead"))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("create deprecated flags for the LibreOffice module: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
fs.Int64("libreoffice-restart-after", 10, "Number of conversions after which LibreOffice will automatically restart. Set to 0 to disable this feature")
|
fs.Int64("libreoffice-restart-after", 10, "Number of conversions after which LibreOffice will automatically restart. Set to 0 to disable this feature")
|
||||||
fs.Bool("libreoffice-auto-start", false, "Automatically launch LibreOffice upon initialization if set to true; otherwise, LibreOffice will start at the time of the first conversion")
|
fs.Bool("libreoffice-auto-start", false, "Automatically launch LibreOffice upon initialization if set to true; otherwise, LibreOffice will start at the time of the first conversion")
|
||||||
fs.Duration("libreoffice-start-timeout", time.Duration(20)*time.Second, "Maximum duration to wait for LibreOffice to start or restart")
|
fs.Duration("libreoffice-start-timeout", time.Duration(20)*time.Second, "Maximum duration to wait for LibreOffice to start or restart")
|
||||||
@@ -107,7 +123,7 @@ func (a *Api) Provision(ctx *gotenberg.Context) error {
|
|||||||
a.args = libreOfficeArguments{
|
a.args = libreOfficeArguments{
|
||||||
binPath: libreOfficeBinPath,
|
binPath: libreOfficeBinPath,
|
||||||
unoBinPath: unoBinPath,
|
unoBinPath: unoBinPath,
|
||||||
startTimeout: flags.MustDuration("libreoffice-start-timeout"),
|
startTimeout: flags.MustDeprecatedDuration("uno-listener-start-timeout", "libreoffice-start-timeout"),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logger.
|
// Logger.
|
||||||
@@ -123,7 +139,7 @@ func (a *Api) Provision(ctx *gotenberg.Context) error {
|
|||||||
|
|
||||||
// Process.
|
// Process.
|
||||||
a.libreOffice = newLibreOfficeProcess(a.args)
|
a.libreOffice = newLibreOfficeProcess(a.args)
|
||||||
a.supervisor = gotenberg.NewProcessSupervisor(a.logger, a.libreOffice, flags.MustInt64("libreoffice-restart-after"))
|
a.supervisor = gotenberg.NewProcessSupervisor(a.logger, a.libreOffice, flags.MustDeprecatedInt64("uno-listener-restart-threshold", "libreoffice-restart-after"))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -188,6 +204,46 @@ func (a *Api) Stop(ctx context.Context) error {
|
|||||||
// Metrics returns the metrics.
|
// Metrics returns the metrics.
|
||||||
func (a *Api) Metrics() ([]gotenberg.Metric, error) {
|
func (a *Api) Metrics() ([]gotenberg.Metric, error) {
|
||||||
return []gotenberg.Metric{
|
return []gotenberg.Metric{
|
||||||
|
// TODO: remove deprecated.
|
||||||
|
{
|
||||||
|
Name: "unoconv_active_instances_count",
|
||||||
|
Description: "Current number of active unoconv instances - deprecated.",
|
||||||
|
Read: func() float64 {
|
||||||
|
return 1
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// TODO: remove deprecated.
|
||||||
|
{
|
||||||
|
Name: "libreoffice_listener_active_instances_count",
|
||||||
|
Description: "Current number of active LibreOffice listener instances - deprecated.",
|
||||||
|
Read: func() float64 {
|
||||||
|
return 1
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// TODO: remove deprecated.
|
||||||
|
{
|
||||||
|
Name: "unoconv_listener_active_instances_count",
|
||||||
|
Description: "Current number of active unoconv listener instances- deprecated.",
|
||||||
|
Read: func() float64 {
|
||||||
|
return 1
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// TODO: remove deprecated.
|
||||||
|
{
|
||||||
|
Name: "libreoffice_listener_queue_length",
|
||||||
|
Description: "Current number of processes in the LibreOffice listener queue - deprecated, prefer libreoffice_requests_queue_size.",
|
||||||
|
Read: func() float64 {
|
||||||
|
return float64(a.supervisor.ReqQueueSize())
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// TODO: remove deprecated.
|
||||||
|
{
|
||||||
|
Name: "unoconv_listener_queue_length",
|
||||||
|
Description: "Current number of processes in the queue - deprecated, prefer libreoffice_requests_queue_size.",
|
||||||
|
Read: func() float64 {
|
||||||
|
return float64(a.supervisor.ReqQueueSize())
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "libreoffice_requests_queue_size",
|
Name: "libreoffice_requests_queue_size",
|
||||||
Description: "Current number of LibreOffice conversion requests waiting to be treated.",
|
Description: "Current number of LibreOffice conversion requests waiting to be treated.",
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
"github.com/alexliesenfeld/health"
|
"github.com/alexliesenfeld/health"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestApi_Descriptor(t *testing.T) {
|
func TestApi_Descriptor(t *testing.T) {
|
||||||
@@ -284,16 +284,41 @@ func TestApi_Metrics(t *testing.T) {
|
|||||||
t.Fatalf("expected no error but got: %v", err)
|
t.Fatalf("expected no error but got: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(metrics) != 2 {
|
if len(metrics) != 7 {
|
||||||
t.Fatalf("expected %d metrics, but got %d", 2, len(metrics))
|
t.Fatalf("expected %d metrics, but got %d", 7, len(metrics))
|
||||||
}
|
}
|
||||||
|
|
||||||
actual := metrics[0].Read()
|
actual := metrics[0].Read()
|
||||||
|
if actual != float64(1) {
|
||||||
|
t.Errorf("expected %f for unoconv_active_instances_count, but got %f", float64(1), actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual = metrics[1].Read()
|
||||||
|
if actual != float64(1) {
|
||||||
|
t.Errorf("expected %f for libreoffice_listener_active_instances_count, but got %f", float64(1), actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual = metrics[2].Read()
|
||||||
|
if actual != float64(1) {
|
||||||
|
t.Errorf("expected %f for unoconv_listener_active_instances_count, but got %f", float64(1), actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual = metrics[3].Read()
|
||||||
|
if actual != float64(10) {
|
||||||
|
t.Errorf("expected %f for libreoffice_listener_queue_length, but got %f", float64(10), actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual = metrics[4].Read()
|
||||||
|
if actual != float64(10) {
|
||||||
|
t.Errorf("expected %f for unoconv_listener_queue_length, but got %f", float64(10), actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual = metrics[5].Read()
|
||||||
if actual != float64(10) {
|
if actual != float64(10) {
|
||||||
t.Errorf("expected %f for libreoffice_requests_queue_size, but got %f", float64(10), actual)
|
t.Errorf("expected %f for libreoffice_requests_queue_size, but got %f", float64(10), actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
actual = metrics[1].Read()
|
actual = metrics[6].Read()
|
||||||
if actual != float64(0) {
|
if actual != float64(0) {
|
||||||
t.Errorf("expected %f for libreoffice_restarts_count, but got %f", float64(0), actual)
|
t.Errorf("expected %f for libreoffice_restarts_count, but got %f", float64(0), actual)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import (
|
|||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||||
)
|
)
|
||||||
|
|
||||||
type libreOffice interface {
|
type libreOffice interface {
|
||||||
@@ -275,6 +275,9 @@ func (p *libreOfficeProcess) pdf(ctx context.Context, logger *zap.Logger, inputP
|
|||||||
|
|
||||||
switch options.PdfFormats.PdfA {
|
switch options.PdfFormats.PdfA {
|
||||||
case "":
|
case "":
|
||||||
|
case gotenberg.PdfA1a:
|
||||||
|
logger.Warn("PDF/A-1a is no more supported by LibreOffice (use PDF/A-1b instead)")
|
||||||
|
args = append(args, "--export", "SelectPdfVersion=1")
|
||||||
case gotenberg.PdfA1b:
|
case gotenberg.PdfA1b:
|
||||||
args = append(args, "--export", "SelectPdfVersion=1")
|
args = append(args, "--export", "SelectPdfVersion=1")
|
||||||
case gotenberg.PdfA2b:
|
case gotenberg.PdfA2b:
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLibreOfficeProcess_Start(t *testing.T) {
|
func TestLibreOfficeProcess_Start(t *testing.T) {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ApiMock is a mock for the [Uno] interface.
|
// ApiMock is a mock for the [Uno] interface.
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import (
|
|||||||
|
|
||||||
flag "github.com/spf13/pflag"
|
flag "github.com/spf13/pflag"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/modules/api"
|
"github.com/gotenberg/gotenberg/v7/pkg/modules/api"
|
||||||
libeofficeapi "github.com/gotenberg/gotenberg/v8/pkg/modules/libreoffice/api"
|
libeofficeapi "github.com/gotenberg/gotenberg/v7/pkg/modules/libreoffice/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||||
libreofficeapi "github.com/gotenberg/gotenberg/v8/pkg/modules/libreoffice/api"
|
libreofficeapi "github.com/gotenberg/gotenberg/v7/pkg/modules/libreoffice/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLibreOffice_Descriptor(t *testing.T) {
|
func TestLibreOffice_Descriptor(t *testing.T) {
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import (
|
|||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/modules/libreoffice/api"
|
"github.com/gotenberg/gotenberg/v7/pkg/modules/libreoffice/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/modules/libreoffice/api"
|
"github.com/gotenberg/gotenberg/v7/pkg/modules/libreoffice/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLibreOfficePdfEngine_Descriptor(t *testing.T) {
|
func TestLibreOfficePdfEngine_Descriptor(t *testing.T) {
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import (
|
|||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/modules/api"
|
"github.com/gotenberg/gotenberg/v7/pkg/modules/api"
|
||||||
libreofficeapi "github.com/gotenberg/gotenberg/v8/pkg/modules/libreoffice/api"
|
libreofficeapi "github.com/gotenberg/gotenberg/v7/pkg/modules/libreoffice/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
// convertRoute returns an [api.Route] which can convert LibreOffice documents
|
// convertRoute returns an [api.Route] which can convert LibreOffice documents
|
||||||
@@ -24,19 +24,25 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap
|
|||||||
|
|
||||||
// Let's get the data from the form and validate them.
|
// Let's get the data from the form and validate them.
|
||||||
var (
|
var (
|
||||||
inputPaths []string
|
inputPaths []string
|
||||||
landscape bool
|
landscape bool
|
||||||
nativePageRanges string
|
nativePageRanges string
|
||||||
pdfa string
|
nativePdfA1aFormat bool
|
||||||
pdfua bool
|
nativePdfFormat string
|
||||||
nativePdfFormats bool
|
pdfFormat string
|
||||||
merge bool
|
pdfa string
|
||||||
|
pdfua bool
|
||||||
|
nativePdfFormats bool
|
||||||
|
merge bool
|
||||||
)
|
)
|
||||||
|
|
||||||
err := ctx.FormData().
|
err := ctx.FormData().
|
||||||
MandatoryPaths(libreOffice.Extensions(), &inputPaths).
|
MandatoryPaths(libreOffice.Extensions(), &inputPaths).
|
||||||
Bool("landscape", &landscape, false).
|
Bool("landscape", &landscape, false).
|
||||||
String("nativePageRanges", &nativePageRanges, "").
|
String("nativePageRanges", &nativePageRanges, "").
|
||||||
|
Bool("nativePdfA1aFormat", &nativePdfA1aFormat, false).
|
||||||
|
String("nativePdfFormat", &nativePdfFormat, "").
|
||||||
|
String("pdfFormat", &pdfFormat, "").
|
||||||
String("pdfa", &pdfa, "").
|
String("pdfa", &pdfa, "").
|
||||||
Bool("pdfua", &pdfua, false).
|
Bool("pdfua", &pdfua, false).
|
||||||
Bool("nativePdfFormats", &nativePdfFormats, true).
|
Bool("nativePdfFormats", &nativePdfFormats, true).
|
||||||
@@ -46,8 +52,42 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap
|
|||||||
return fmt.Errorf("validate form data: %w", err)
|
return fmt.Errorf("validate form data: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: deprecated.
|
||||||
|
// pdfa > nativePdfFormat > pdfFormat > nativePdfA1aFormat.
|
||||||
|
var (
|
||||||
|
actualPdfArchive string
|
||||||
|
nativeFormats bool
|
||||||
|
)
|
||||||
|
|
||||||
|
if nativePdfA1aFormat {
|
||||||
|
ctx.Log().Warn("'nativePdfA1aFormat' is deprecated; prefer the 'pdfa' form field instead")
|
||||||
|
actualPdfArchive = gotenberg.PdfA1a
|
||||||
|
nativeFormats = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if pdfFormat != "" {
|
||||||
|
ctx.Log().Warn("'pdfFormat' is deprecated; prefer the 'pdfa' form field instead")
|
||||||
|
actualPdfArchive = pdfFormat
|
||||||
|
nativeFormats = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if nativePdfFormat != "" {
|
||||||
|
ctx.Log().Warn("'nativePdfFormat' is deprecated; prefer the 'pdfa' form field instead")
|
||||||
|
actualPdfArchive = nativePdfFormat
|
||||||
|
nativeFormats = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if pdfa != "" {
|
||||||
|
actualPdfArchive = pdfa
|
||||||
|
nativeFormats = nativePdfFormats
|
||||||
|
}
|
||||||
|
|
||||||
|
if pdfua {
|
||||||
|
nativeFormats = nativePdfFormats
|
||||||
|
}
|
||||||
|
|
||||||
pdfFormats := gotenberg.PdfFormats{
|
pdfFormats := gotenberg.PdfFormats{
|
||||||
PdfA: pdfa,
|
PdfA: actualPdfArchive,
|
||||||
PdfUa: pdfua,
|
PdfUa: pdfua,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,7 +101,7 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap
|
|||||||
PageRanges: nativePageRanges,
|
PageRanges: nativePageRanges,
|
||||||
}
|
}
|
||||||
|
|
||||||
if nativePdfFormats {
|
if nativeFormats {
|
||||||
options.PdfFormats = pdfFormats
|
options.PdfFormats = pdfFormats
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,7 +132,7 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap
|
|||||||
// Now, let's check if the client want to convert this result
|
// Now, let's check if the client want to convert this result
|
||||||
// PDF to specific PDF formats.
|
// PDF to specific PDF formats.
|
||||||
zeroValued := gotenberg.PdfFormats{}
|
zeroValued := gotenberg.PdfFormats{}
|
||||||
if !nativePdfFormats && pdfFormats != zeroValued {
|
if !nativeFormats && pdfFormats != zeroValued {
|
||||||
convertInputPath := outputPath
|
convertInputPath := outputPath
|
||||||
convertOutputPath := ctx.GeneratePath(".pdf")
|
convertOutputPath := ctx.GeneratePath(".pdf")
|
||||||
|
|
||||||
@@ -129,7 +169,7 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap
|
|||||||
// Ok, we don't have to merge the PDFs. Let's check if the client
|
// Ok, we don't have to merge the PDFs. Let's check if the client
|
||||||
// want to convert each PDF to a specific PDF format.
|
// want to convert each PDF to a specific PDF format.
|
||||||
zeroValued := gotenberg.PdfFormats{}
|
zeroValued := gotenberg.PdfFormats{}
|
||||||
if !nativePdfFormats && pdfFormats != zeroValued {
|
if !nativeFormats && pdfFormats != zeroValued {
|
||||||
convertOutputPaths := make([]string, len(outputPaths))
|
convertOutputPaths := make([]string, len(outputPaths))
|
||||||
|
|
||||||
for i, outputPath := range outputPaths {
|
for i, outputPath := range outputPaths {
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ import (
|
|||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/modules/api"
|
"github.com/gotenberg/gotenberg/v7/pkg/modules/api"
|
||||||
libreofficeapi "github.com/gotenberg/gotenberg/v8/pkg/modules/libreoffice/api"
|
libreofficeapi "github.com/gotenberg/gotenberg/v7/pkg/modules/libreoffice/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestConvertRoute(t *testing.T) {
|
func TestConvertRoute(t *testing.T) {
|
||||||
@@ -252,19 +252,31 @@ func TestConvertRoute(t *testing.T) {
|
|||||||
expectOutputPathsCount: 1,
|
expectOutputPathsCount: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "success with native PDF/A & PDF/UA (single file)",
|
scenario: "success with every non-native PDF/A & PDF/UA form fields (single file)",
|
||||||
ctx: func() *api.ContextMock {
|
ctx: func() *api.ContextMock {
|
||||||
ctx := &api.ContextMock{Context: new(api.Context)}
|
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||||
ctx.SetFiles(map[string]string{
|
ctx.SetFiles(map[string]string{
|
||||||
"document.docx": "/document.docx",
|
"document.docx": "/document.docx",
|
||||||
})
|
})
|
||||||
ctx.SetValues(map[string][]string{
|
ctx.SetValues(map[string][]string{
|
||||||
|
"nativePdfA1aFormat": {
|
||||||
|
"true",
|
||||||
|
},
|
||||||
|
"pdfFormat": {
|
||||||
|
gotenberg.PdfA1b,
|
||||||
|
},
|
||||||
|
"nativePdfFormat": {
|
||||||
|
gotenberg.PdfA1b,
|
||||||
|
},
|
||||||
"pdfa": {
|
"pdfa": {
|
||||||
gotenberg.PdfA1b,
|
gotenberg.PdfA1b,
|
||||||
},
|
},
|
||||||
"pdfua": {
|
"pdfua": {
|
||||||
"true",
|
"true",
|
||||||
},
|
},
|
||||||
|
"nativePdfFormats": {
|
||||||
|
"false",
|
||||||
|
},
|
||||||
})
|
})
|
||||||
return ctx
|
return ctx
|
||||||
}(),
|
}(),
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
"go.uber.org/zap/zapcore"
|
"go.uber.org/zap/zapcore"
|
||||||
"golang.org/x/term"
|
"golang.org/x/term"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"go.uber.org/zap/zapcore"
|
"go.uber.org/zap/zapcore"
|
||||||
"go.uber.org/zap/zaptest/observer"
|
"go.uber.org/zap/zaptest/observer"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLogging_Descriptor(t *testing.T) {
|
func TestLogging_Descriptor(t *testing.T) {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
pdfcpuConfig "github.com/pdfcpu/pdfcpu/pkg/pdfcpu/model"
|
pdfcpuConfig "github.com/pdfcpu/pdfcpu/pkg/pdfcpu/model"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPdfCpu_Descriptor(t *testing.T) {
|
func TestPdfCpu_Descriptor(t *testing.T) {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"go.uber.org/multierr"
|
"go.uber.org/multierr"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||||
)
|
)
|
||||||
|
|
||||||
type multiPdfEngines struct {
|
type multiPdfEngines struct {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMultiPdfEngines_Merge(t *testing.T) {
|
func TestMultiPdfEngines_Merge(t *testing.T) {
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import (
|
|||||||
|
|
||||||
flag "github.com/spf13/pflag"
|
flag "github.com/spf13/pflag"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/modules/api"
|
"github.com/gotenberg/gotenberg/v7/pkg/modules/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -54,6 +54,18 @@ func (mod *PdfEngines) Provision(ctx *gotenberg.Context) error {
|
|||||||
names := flags.MustStringSlice("pdfengines-engines")
|
names := flags.MustStringSlice("pdfengines-engines")
|
||||||
mod.disableRoutes = flags.MustBool("pdfengines-disable-routes")
|
mod.disableRoutes = flags.MustBool("pdfengines-disable-routes")
|
||||||
|
|
||||||
|
loggerProvider, err := ctx.Module(new(gotenberg.LoggerProvider))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("get logger provider: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger, err := loggerProvider.(gotenberg.LoggerProvider).Logger(mod)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("get logger: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger = logger.Named("pdfengines")
|
||||||
|
|
||||||
engines, err := ctx.Modules(new(gotenberg.PdfEngine))
|
engines, err := ctx.Modules(new(gotenberg.PdfEngine))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("get PDF engines: %w", err)
|
return fmt.Errorf("get PDF engines: %w", err)
|
||||||
@@ -69,13 +81,13 @@ func (mod *PdfEngines) Provision(ctx *gotenberg.Context) error {
|
|||||||
// Selection from user.
|
// Selection from user.
|
||||||
mod.names = names
|
mod.names = names
|
||||||
|
|
||||||
// Example in case of deprecated module name.
|
for i, name := range names {
|
||||||
//for i, name := range names {
|
// FIXME: deprecated.
|
||||||
// if name == "unoconv-pdfengine" || name == "uno-pdfengine" {
|
if name == "unoconv-pdfengine" || name == "uno-pdfengine" {
|
||||||
// logger.Warn(fmt.Sprintf("%s is deprecated; prefer libreoffice-pdfengine instead", name))
|
logger.Warn(fmt.Sprintf("%s is deprecated; prefer libreoffice-pdfengine instead", name))
|
||||||
// mod.names[i] = "libreoffice-pdfengine"
|
mod.names[i] = "libreoffice-pdfengine"
|
||||||
// }
|
}
|
||||||
//}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
|
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPdfEngines_Descriptor(t *testing.T) {
|
func TestPdfEngines_Descriptor(t *testing.T) {
|
||||||
@@ -32,12 +34,16 @@ func TestPdfEngines_Provision(t *testing.T) {
|
|||||||
ctx: func() *gotenberg.Context {
|
ctx: func() *gotenberg.Context {
|
||||||
provider := &struct {
|
provider := &struct {
|
||||||
gotenberg.ModuleMock
|
gotenberg.ModuleMock
|
||||||
|
gotenberg.LoggerProviderMock
|
||||||
}{}
|
}{}
|
||||||
provider.DescriptorMock = func() gotenberg.ModuleDescriptor {
|
provider.DescriptorMock = func() gotenberg.ModuleDescriptor {
|
||||||
return gotenberg.ModuleDescriptor{ID: "foo", New: func() gotenberg.Module {
|
return gotenberg.ModuleDescriptor{ID: "foo", New: func() gotenberg.Module {
|
||||||
return provider
|
return provider
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
provider.LoggerMock = func(mod gotenberg.Module) (*zap.Logger, error) {
|
||||||
|
return zap.NewNop(), nil
|
||||||
|
}
|
||||||
|
|
||||||
engine := &struct {
|
engine := &struct {
|
||||||
gotenberg.ModuleMock
|
gotenberg.ModuleMock
|
||||||
@@ -69,12 +75,17 @@ func TestPdfEngines_Provision(t *testing.T) {
|
|||||||
ctx: func() *gotenberg.Context {
|
ctx: func() *gotenberg.Context {
|
||||||
provider := &struct {
|
provider := &struct {
|
||||||
gotenberg.ModuleMock
|
gotenberg.ModuleMock
|
||||||
|
gotenberg.LoggerProviderMock
|
||||||
}{}
|
}{}
|
||||||
provider.DescriptorMock = func() gotenberg.ModuleDescriptor {
|
provider.DescriptorMock = func() gotenberg.ModuleDescriptor {
|
||||||
return gotenberg.ModuleDescriptor{ID: "foo", New: func() gotenberg.Module {
|
return gotenberg.ModuleDescriptor{ID: "foo", New: func() gotenberg.Module {
|
||||||
return provider
|
return provider
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
provider.LoggerMock = func(mod gotenberg.Module) (*zap.Logger, error) {
|
||||||
|
return zap.NewNop(), nil
|
||||||
|
}
|
||||||
|
|
||||||
engine1 := &struct {
|
engine1 := &struct {
|
||||||
gotenberg.ModuleMock
|
gotenberg.ModuleMock
|
||||||
gotenberg.ValidatorMock
|
gotenberg.ValidatorMock
|
||||||
@@ -120,16 +131,107 @@ func TestPdfEngines_Provision(t *testing.T) {
|
|||||||
expectError: false,
|
expectError: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "no valid PDF engine",
|
scenario: "user select deprecated unoconv-pdfengine",
|
||||||
ctx: func() *gotenberg.Context {
|
ctx: func() *gotenberg.Context {
|
||||||
provider := &struct {
|
provider := &struct {
|
||||||
gotenberg.ModuleMock
|
gotenberg.ModuleMock
|
||||||
|
gotenberg.LoggerProviderMock
|
||||||
}{}
|
}{}
|
||||||
provider.DescriptorMock = func() gotenberg.ModuleDescriptor {
|
provider.DescriptorMock = func() gotenberg.ModuleDescriptor {
|
||||||
return gotenberg.ModuleDescriptor{ID: "foo", New: func() gotenberg.Module {
|
return gotenberg.ModuleDescriptor{ID: "foo", New: func() gotenberg.Module {
|
||||||
return provider
|
return provider
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
provider.LoggerMock = func(mod gotenberg.Module) (*zap.Logger, error) {
|
||||||
|
return zap.NewNop(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
engine := &struct {
|
||||||
|
gotenberg.ModuleMock
|
||||||
|
gotenberg.ValidatorMock
|
||||||
|
gotenberg.PdfEngineMock
|
||||||
|
}{}
|
||||||
|
engine.DescriptorMock = func() gotenberg.ModuleDescriptor {
|
||||||
|
return gotenberg.ModuleDescriptor{ID: "libreoffice-pdfengine", New: func() gotenberg.Module { return engine }}
|
||||||
|
}
|
||||||
|
engine.ValidateMock = func() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
fs := new(PdfEngines).Descriptor().FlagSet
|
||||||
|
err := fs.Parse([]string{"--pdfengines-engines=unoconv-pdfengine"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no error but got: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return gotenberg.NewContext(
|
||||||
|
gotenberg.ParsedFlags{
|
||||||
|
FlagSet: fs,
|
||||||
|
},
|
||||||
|
[]gotenberg.ModuleDescriptor{
|
||||||
|
provider.Descriptor(),
|
||||||
|
engine.Descriptor(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}(),
|
||||||
|
expectedPdfEngines: []string{"libreoffice-pdfengine"},
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "no logger provider",
|
||||||
|
ctx: func() *gotenberg.Context {
|
||||||
|
return gotenberg.NewContext(
|
||||||
|
gotenberg.ParsedFlags{
|
||||||
|
FlagSet: new(PdfEngines).Descriptor().FlagSet,
|
||||||
|
},
|
||||||
|
[]gotenberg.ModuleDescriptor{},
|
||||||
|
)
|
||||||
|
}(),
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "no logger from logger provider",
|
||||||
|
ctx: func() *gotenberg.Context {
|
||||||
|
provider := &struct {
|
||||||
|
gotenberg.ModuleMock
|
||||||
|
gotenberg.LoggerProviderMock
|
||||||
|
}{}
|
||||||
|
provider.DescriptorMock = func() gotenberg.ModuleDescriptor {
|
||||||
|
return gotenberg.ModuleDescriptor{ID: "foo", New: func() gotenberg.Module {
|
||||||
|
return provider
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
provider.LoggerMock = func(mod gotenberg.Module) (*zap.Logger, error) {
|
||||||
|
return nil, errors.New("foo")
|
||||||
|
}
|
||||||
|
|
||||||
|
return gotenberg.NewContext(
|
||||||
|
gotenberg.ParsedFlags{
|
||||||
|
FlagSet: new(PdfEngines).Descriptor().FlagSet,
|
||||||
|
},
|
||||||
|
[]gotenberg.ModuleDescriptor{
|
||||||
|
provider.Descriptor(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}(),
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "no valid PDF engine",
|
||||||
|
ctx: func() *gotenberg.Context {
|
||||||
|
provider := &struct {
|
||||||
|
gotenberg.ModuleMock
|
||||||
|
gotenberg.LoggerProviderMock
|
||||||
|
}{}
|
||||||
|
provider.DescriptorMock = func() gotenberg.ModuleDescriptor {
|
||||||
|
return gotenberg.ModuleDescriptor{ID: "foo", New: func() gotenberg.Module {
|
||||||
|
return provider
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
provider.LoggerMock = func(mod gotenberg.Module) (*zap.Logger, error) {
|
||||||
|
return zap.NewNop(), nil
|
||||||
|
}
|
||||||
|
|
||||||
engine := &struct {
|
engine := &struct {
|
||||||
gotenberg.ModuleMock
|
gotenberg.ModuleMock
|
||||||
gotenberg.ValidatorMock
|
gotenberg.ValidatorMock
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import (
|
|||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/modules/api"
|
"github.com/gotenberg/gotenberg/v7/pkg/modules/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
// mergeRoute returns an [api.Route] which can merge PDFs.
|
// mergeRoute returns an [api.Route] which can merge PDFs.
|
||||||
@@ -23,12 +23,14 @@ func mergeRoute(engine gotenberg.PdfEngine) api.Route {
|
|||||||
// Let's get the data from the form and validate them.
|
// Let's get the data from the form and validate them.
|
||||||
var (
|
var (
|
||||||
inputPaths []string
|
inputPaths []string
|
||||||
|
pdfFormat string
|
||||||
pdfa string
|
pdfa string
|
||||||
pdfua bool
|
pdfua bool
|
||||||
)
|
)
|
||||||
|
|
||||||
err := ctx.FormData().
|
err := ctx.FormData().
|
||||||
MandatoryPaths([]string{".pdf"}, &inputPaths).
|
MandatoryPaths([]string{".pdf"}, &inputPaths).
|
||||||
|
String("pdfFormat", &pdfFormat, "").
|
||||||
String("pdfa", &pdfa, "").
|
String("pdfa", &pdfa, "").
|
||||||
Bool("pdfua", &pdfua, false).
|
Bool("pdfua", &pdfua, false).
|
||||||
Validate()
|
Validate()
|
||||||
@@ -36,8 +38,20 @@ func mergeRoute(engine gotenberg.PdfEngine) api.Route {
|
|||||||
return fmt.Errorf("validate form data: %w", err)
|
return fmt.Errorf("validate form data: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var actualPdfArchive string
|
||||||
|
|
||||||
|
if pdfFormat != "" {
|
||||||
|
// FIXME: deprecated
|
||||||
|
ctx.Log().Warn("'pdfFormat' is deprecated; prefer the 'pdfa' form field instead")
|
||||||
|
actualPdfArchive = pdfFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
if pdfa != "" {
|
||||||
|
actualPdfArchive = pdfa
|
||||||
|
}
|
||||||
|
|
||||||
pdfFormats := gotenberg.PdfFormats{
|
pdfFormats := gotenberg.PdfFormats{
|
||||||
PdfA: pdfa,
|
PdfA: actualPdfArchive,
|
||||||
PdfUa: pdfua,
|
PdfUa: pdfua,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,12 +118,14 @@ func convertRoute(engine gotenberg.PdfEngine) api.Route {
|
|||||||
// Let's get the data from the form and validate them.
|
// Let's get the data from the form and validate them.
|
||||||
var (
|
var (
|
||||||
inputPaths []string
|
inputPaths []string
|
||||||
|
pdfFormat string
|
||||||
pdfa string
|
pdfa string
|
||||||
pdfua bool
|
pdfua bool
|
||||||
)
|
)
|
||||||
|
|
||||||
err := ctx.FormData().
|
err := ctx.FormData().
|
||||||
MandatoryPaths([]string{".pdf"}, &inputPaths).
|
MandatoryPaths([]string{".pdf"}, &inputPaths).
|
||||||
|
String("pdfFormat", &pdfFormat, "").
|
||||||
String("pdfa", &pdfa, "").
|
String("pdfa", &pdfa, "").
|
||||||
Bool("pdfua", &pdfua, false).
|
Bool("pdfua", &pdfua, false).
|
||||||
Validate()
|
Validate()
|
||||||
@@ -117,8 +133,20 @@ func convertRoute(engine gotenberg.PdfEngine) api.Route {
|
|||||||
return fmt.Errorf("validate form data: %w", err)
|
return fmt.Errorf("validate form data: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var actualPdfArchive string
|
||||||
|
|
||||||
|
if pdfFormat != "" {
|
||||||
|
// FIXME: deprecated.
|
||||||
|
ctx.Log().Warn("'pdfFormat' is deprecated; prefer the 'pdfa' form field instead")
|
||||||
|
actualPdfArchive = pdfFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
if pdfa != "" {
|
||||||
|
actualPdfArchive = pdfa
|
||||||
|
}
|
||||||
|
|
||||||
pdfFormats := gotenberg.PdfFormats{
|
pdfFormats := gotenberg.PdfFormats{
|
||||||
PdfA: pdfa,
|
PdfA: actualPdfArchive,
|
||||||
PdfUa: pdfua,
|
PdfUa: pdfua,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import (
|
|||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/modules/api"
|
"github.com/gotenberg/gotenberg/v7/pkg/modules/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMergeHandler(t *testing.T) {
|
func TestMergeHandler(t *testing.T) {
|
||||||
@@ -145,7 +145,7 @@ func TestMergeHandler(t *testing.T) {
|
|||||||
expectOutputPathsCount: 0,
|
expectOutputPathsCount: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "success with PDF/A & PDF/UA form fields",
|
scenario: "success with every PDF/A & PDF/UA form fields",
|
||||||
ctx: func() *api.ContextMock {
|
ctx: func() *api.ContextMock {
|
||||||
ctx := &api.ContextMock{Context: new(api.Context)}
|
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||||
ctx.SetFiles(map[string]string{
|
ctx.SetFiles(map[string]string{
|
||||||
@@ -153,6 +153,9 @@ func TestMergeHandler(t *testing.T) {
|
|||||||
"file2.pdf": "/file2.pdf",
|
"file2.pdf": "/file2.pdf",
|
||||||
})
|
})
|
||||||
ctx.SetValues(map[string][]string{
|
ctx.SetValues(map[string][]string{
|
||||||
|
"pdfFormat": {
|
||||||
|
gotenberg.PdfA1a,
|
||||||
|
},
|
||||||
"pdfa": {
|
"pdfa": {
|
||||||
gotenberg.PdfA1b,
|
gotenberg.PdfA1b,
|
||||||
},
|
},
|
||||||
@@ -319,13 +322,16 @@ func TestConvertHandler(t *testing.T) {
|
|||||||
expectOutputPathsCount: 0,
|
expectOutputPathsCount: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "success with PDF/A & PDF/UA form fields (single file)",
|
scenario: "success with every PDF/A & PDF/UA form fields (single file)",
|
||||||
ctx: func() *api.ContextMock {
|
ctx: func() *api.ContextMock {
|
||||||
ctx := &api.ContextMock{Context: new(api.Context)}
|
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||||
ctx.SetFiles(map[string]string{
|
ctx.SetFiles(map[string]string{
|
||||||
"file.pdf": "/file.pdf",
|
"file.pdf": "/file.pdf",
|
||||||
})
|
})
|
||||||
ctx.SetValues(map[string][]string{
|
ctx.SetValues(map[string][]string{
|
||||||
|
"pdfFormat": {
|
||||||
|
gotenberg.PdfA1a,
|
||||||
|
},
|
||||||
"pdfa": {
|
"pdfa": {
|
||||||
gotenberg.PdfA1b,
|
gotenberg.PdfA1b,
|
||||||
},
|
},
|
||||||
@@ -345,7 +351,7 @@ func TestConvertHandler(t *testing.T) {
|
|||||||
expectOutputPathsCount: 1,
|
expectOutputPathsCount: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "success with PDF/A & PDF/UA form fields (many files)",
|
scenario: "success with every PDF/A & PDF/UA form fields (many files)",
|
||||||
ctx: func() *api.ContextMock {
|
ctx: func() *api.ContextMock {
|
||||||
ctx := &api.ContextMock{Context: new(api.Context)}
|
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||||
ctx.SetFiles(map[string]string{
|
ctx.SetFiles(map[string]string{
|
||||||
@@ -353,6 +359,9 @@ func TestConvertHandler(t *testing.T) {
|
|||||||
"file2.pdf": "/file2.pdf",
|
"file2.pdf": "/file2.pdf",
|
||||||
})
|
})
|
||||||
ctx.SetValues(map[string][]string{
|
ctx.SetValues(map[string][]string{
|
||||||
|
"pdfFormat": {
|
||||||
|
gotenberg.PdfA1a,
|
||||||
|
},
|
||||||
"pdfa": {
|
"pdfa": {
|
||||||
gotenberg.PdfA1b,
|
gotenberg.PdfA1b,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,10 +5,11 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -51,6 +52,23 @@ func (engine *PdfTk) Validate() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Metrics returns the metrics.
|
||||||
|
func (engine *PdfTk) Metrics() ([]gotenberg.Metric, error) {
|
||||||
|
// TODO: remove deprecated.
|
||||||
|
return []gotenberg.Metric{
|
||||||
|
{
|
||||||
|
Name: "pdftk_active_instances_count",
|
||||||
|
Description: "Current number of active PDFtk instances - deprecated.",
|
||||||
|
Read: func() float64 {
|
||||||
|
activeInstancesCountMu.RLock()
|
||||||
|
defer activeInstancesCountMu.RUnlock()
|
||||||
|
|
||||||
|
return activeInstancesCount
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Merge combines multiple PDFs into a single PDF.
|
// Merge combines multiple PDFs into a single PDF.
|
||||||
func (engine *PdfTk) Merge(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error {
|
func (engine *PdfTk) Merge(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error {
|
||||||
var args []string
|
var args []string
|
||||||
@@ -62,7 +80,16 @@ func (engine *PdfTk) Merge(ctx context.Context, logger *zap.Logger, inputPaths [
|
|||||||
return fmt.Errorf("create command: %w", err)
|
return fmt.Errorf("create command: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
activeInstancesCountMu.Lock()
|
||||||
|
activeInstancesCount += 1
|
||||||
|
activeInstancesCountMu.Unlock()
|
||||||
|
|
||||||
_, err = cmd.Exec()
|
_, err = cmd.Exec()
|
||||||
|
|
||||||
|
activeInstancesCountMu.Lock()
|
||||||
|
activeInstancesCount -= 1
|
||||||
|
activeInstancesCountMu.Unlock()
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -75,10 +102,16 @@ func (engine *PdfTk) Convert(ctx context.Context, logger *zap.Logger, formats go
|
|||||||
return fmt.Errorf("convert PDF to '%+v' with PDFtk: %w", formats, gotenberg.ErrPdfEngineMethodNotSupported)
|
return fmt.Errorf("convert PDF to '%+v' with PDFtk: %w", formats, gotenberg.ErrPdfEngineMethodNotSupported)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
activeInstancesCount float64
|
||||||
|
activeInstancesCountMu sync.RWMutex
|
||||||
|
)
|
||||||
|
|
||||||
// Interface guards.
|
// Interface guards.
|
||||||
var (
|
var (
|
||||||
_ gotenberg.Module = (*PdfTk)(nil)
|
_ gotenberg.Module = (*PdfTk)(nil)
|
||||||
_ gotenberg.Provisioner = (*PdfTk)(nil)
|
_ gotenberg.Provisioner = (*PdfTk)(nil)
|
||||||
_ gotenberg.Validator = (*PdfTk)(nil)
|
_ gotenberg.Validator = (*PdfTk)(nil)
|
||||||
_ gotenberg.PdfEngine = (*PdfTk)(nil)
|
_ gotenberg.MetricsProvider = (*PdfTk)(nil)
|
||||||
|
_ gotenberg.PdfEngine = (*PdfTk)(nil)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPdfTk_Descriptor(t *testing.T) {
|
func TestPdfTk_Descriptor(t *testing.T) {
|
||||||
@@ -71,6 +71,22 @@ func TestPdfTk_Validate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPdfTk_Metrics(t *testing.T) {
|
||||||
|
metrics, err := new(PdfTk).Metrics()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no error but got: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(metrics) != 1 {
|
||||||
|
t.Fatalf("expected %d metrics, but got %d", 1, len(metrics))
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := metrics[0].Read()
|
||||||
|
if actual != 0 {
|
||||||
|
t.Errorf("expected %d PDFtk instances, but got %f", 0, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestPdfTk_Merge(t *testing.T) {
|
func TestPdfTk_Merge(t *testing.T) {
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
scenario string
|
scenario string
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ import (
|
|||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
flag "github.com/spf13/pflag"
|
flag "github.com/spf13/pflag"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/modules/api"
|
"github.com/gotenberg/gotenberg/v7/pkg/modules/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPrometheus_Descriptor(t *testing.T) {
|
func TestPrometheus_Descriptor(t *testing.T) {
|
||||||
|
|||||||
@@ -5,10 +5,11 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -51,6 +52,23 @@ func (engine *QPdf) Validate() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Metrics returns the metrics.
|
||||||
|
func (engine *QPdf) Metrics() ([]gotenberg.Metric, error) {
|
||||||
|
// TODO: remove deprecated.
|
||||||
|
return []gotenberg.Metric{
|
||||||
|
{
|
||||||
|
Name: "qpdf_active_instances_count",
|
||||||
|
Description: "Current number of active QPDF instances - deprecated.",
|
||||||
|
Read: func() float64 {
|
||||||
|
activeInstancesCountMu.RLock()
|
||||||
|
defer activeInstancesCountMu.RUnlock()
|
||||||
|
|
||||||
|
return activeInstancesCount
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Merge combines multiple PDFs into a single PDF.
|
// Merge combines multiple PDFs into a single PDF.
|
||||||
func (engine *QPdf) Merge(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error {
|
func (engine *QPdf) Merge(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error {
|
||||||
var args []string
|
var args []string
|
||||||
@@ -64,7 +82,16 @@ func (engine *QPdf) Merge(ctx context.Context, logger *zap.Logger, inputPaths []
|
|||||||
return fmt.Errorf("create command: %w", err)
|
return fmt.Errorf("create command: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
activeInstancesCountMu.Lock()
|
||||||
|
activeInstancesCount += 1
|
||||||
|
activeInstancesCountMu.Unlock()
|
||||||
|
|
||||||
_, err = cmd.Exec()
|
_, err = cmd.Exec()
|
||||||
|
|
||||||
|
activeInstancesCountMu.Lock()
|
||||||
|
activeInstancesCount -= 1
|
||||||
|
activeInstancesCountMu.Unlock()
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -78,8 +105,14 @@ func (engine *QPdf) Convert(ctx context.Context, logger *zap.Logger, formats got
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_ gotenberg.Module = (*QPdf)(nil)
|
activeInstancesCount float64
|
||||||
_ gotenberg.Provisioner = (*QPdf)(nil)
|
activeInstancesCountMu sync.RWMutex
|
||||||
_ gotenberg.Validator = (*QPdf)(nil)
|
)
|
||||||
_ gotenberg.PdfEngine = (*QPdf)(nil)
|
|
||||||
|
var (
|
||||||
|
_ gotenberg.Module = (*QPdf)(nil)
|
||||||
|
_ gotenberg.Provisioner = (*QPdf)(nil)
|
||||||
|
_ gotenberg.Validator = (*QPdf)(nil)
|
||||||
|
_ gotenberg.MetricsProvider = (*QPdf)(nil)
|
||||||
|
_ gotenberg.PdfEngine = (*QPdf)(nil)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestQPdf_Descriptor(t *testing.T) {
|
func TestQPdf_Descriptor(t *testing.T) {
|
||||||
@@ -71,6 +71,22 @@ func TestQPdf_Validate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestQPdf_Metrics(t *testing.T) {
|
||||||
|
metrics, err := new(QPdf).Metrics()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error but got: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(metrics) != 1 {
|
||||||
|
t.Errorf("expected %d metrics, but got %d", 1, len(metrics))
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := metrics[0].Read()
|
||||||
|
if actual != 0 {
|
||||||
|
t.Errorf("expected %d QPdf instances, but got %f", 0, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestQPdf_Merge(t *testing.T) {
|
func TestQPdf_Merge(t *testing.T) {
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
scenario string
|
scenario string
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import (
|
|||||||
"github.com/hashicorp/go-retryablehttp"
|
"github.com/hashicorp/go-retryablehttp"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/modules/api"
|
"github.com/gotenberg/gotenberg/v7/pkg/modules/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
func webhookMiddleware(w *Webhook) api.Middleware {
|
func webhookMiddleware(w *Webhook) api.Middleware {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import (
|
|||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/modules/api"
|
"github.com/gotenberg/gotenberg/v7/pkg/modules/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestWebhookMiddlewareGuards(t *testing.T) {
|
func TestWebhookMiddlewareGuards(t *testing.T) {
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
package webhook
|
package webhook
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
flag "github.com/spf13/pflag"
|
flag "github.com/spf13/pflag"
|
||||||
|
"go.uber.org/multierr"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/modules/api"
|
"github.com/gotenberg/gotenberg/v7/pkg/modules/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -34,6 +36,31 @@ func (w *Webhook) Descriptor() gotenberg.ModuleDescriptor {
|
|||||||
ID: "webhook",
|
ID: "webhook",
|
||||||
FlagSet: func() *flag.FlagSet {
|
FlagSet: func() *flag.FlagSet {
|
||||||
fs := flag.NewFlagSet("webhook", flag.ExitOnError)
|
fs := flag.NewFlagSet("webhook", flag.ExitOnError)
|
||||||
|
// Deprecated flags.
|
||||||
|
fs.String("api-webhook-allow-list", "", "Set the allowed URLs for the webhook feature using a regular expression")
|
||||||
|
fs.String("api-webhook-deny-list", "", "Set the denied URLs for the webhook feature using a regular expression")
|
||||||
|
fs.String("api-webhook-error-allow-list", "", "Set the allowed URLs in case of an error for the webhook feature using a regular expression")
|
||||||
|
fs.String("api-webhook-error-deny-list", "", "Set the denied URLs in case of an error for the webhook feature using a regular expression")
|
||||||
|
fs.Int("api-webhook-max-retry", 4, "Set the maximum number of retries for the webhook feature")
|
||||||
|
fs.Duration("api-webhook-retry-min-wait", time.Duration(1)*time.Second, "Set the minimum duration to wait before trying to call the webhook again")
|
||||||
|
fs.Duration("api-webhook-retry-max-wait", time.Duration(30)*time.Second, "Set the maximum duration to wait before trying to call the webhook again")
|
||||||
|
fs.Bool("api-disable-webhook", false, "Disable the webhook feature")
|
||||||
|
|
||||||
|
var err error
|
||||||
|
err = multierr.Append(err, fs.MarkDeprecated("api-webhook-allow-list", "use webhook-allow-list instead"))
|
||||||
|
err = multierr.Append(err, fs.MarkDeprecated("api-webhook-deny-list", "use webhook-deny-list instead"))
|
||||||
|
err = multierr.Append(err, fs.MarkDeprecated("api-webhook-error-allow-list", "use webhook-error-allow-list instead"))
|
||||||
|
err = multierr.Append(err, fs.MarkDeprecated("api-webhook-error-deny-list", "use webhook-error-deny-list instead"))
|
||||||
|
err = multierr.Append(err, fs.MarkDeprecated("api-webhook-max-retry", "use webhook-max-retry instead"))
|
||||||
|
err = multierr.Append(err, fs.MarkDeprecated("api-webhook-retry-min-wait", "use webhook-retry-min-wait instead"))
|
||||||
|
err = multierr.Append(err, fs.MarkDeprecated("api-webhook-retry-max-wait", "use webhook-retry-max-wait instead"))
|
||||||
|
err = multierr.Append(err, fs.MarkDeprecated("api-disable-webhook", "use webhook-disable instead"))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("create deprecated flags for the webhook module: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// New flags.
|
||||||
fs.String("webhook-allow-list", "", "Set the allowed URLs for the webhook feature using a regular expression")
|
fs.String("webhook-allow-list", "", "Set the allowed URLs for the webhook feature using a regular expression")
|
||||||
fs.String("webhook-deny-list", "", "Set the denied URLs for the webhook feature using a regular expression")
|
fs.String("webhook-deny-list", "", "Set the denied URLs for the webhook feature using a regular expression")
|
||||||
fs.String("webhook-error-allow-list", "", "Set the allowed URLs in case of an error for the webhook feature using a regular expression")
|
fs.String("webhook-error-allow-list", "", "Set the allowed URLs in case of an error for the webhook feature using a regular expression")
|
||||||
@@ -53,15 +80,15 @@ func (w *Webhook) Descriptor() gotenberg.ModuleDescriptor {
|
|||||||
// Provision sets the module properties.
|
// Provision sets the module properties.
|
||||||
func (w *Webhook) Provision(ctx *gotenberg.Context) error {
|
func (w *Webhook) Provision(ctx *gotenberg.Context) error {
|
||||||
flags := ctx.ParsedFlags()
|
flags := ctx.ParsedFlags()
|
||||||
w.allowList = flags.MustRegexp("webhook-allow-list")
|
w.allowList = flags.MustDeprecatedRegexp("api-webhook-allow-list", "webhook-allow-list")
|
||||||
w.denyList = flags.MustRegexp("webhook-deny-list")
|
w.denyList = flags.MustDeprecatedRegexp("api-webhook-deny-list", "webhook-deny-list")
|
||||||
w.errorAllowList = flags.MustRegexp("webhook-error-allow-list")
|
w.errorAllowList = flags.MustDeprecatedRegexp("api-webhook-error-allow-list", "webhook-error-allow-list")
|
||||||
w.errorDenyList = flags.MustRegexp("webhook-error-deny-list")
|
w.errorDenyList = flags.MustDeprecatedRegexp("api-webhook-error-deny-list", "webhook-error-deny-list")
|
||||||
w.maxRetry = flags.MustInt("webhook-max-retry")
|
w.maxRetry = flags.MustDeprecatedInt("api-webhook-max-retry", "webhook-max-retry")
|
||||||
w.retryMinWait = flags.MustDuration("webhook-retry-min-wait")
|
w.retryMinWait = flags.MustDeprecatedDuration("api-webhook-retry-min-wait", "webhook-retry-min-wait")
|
||||||
w.retryMaxWait = flags.MustDuration("webhook-retry-max-wait")
|
w.retryMaxWait = flags.MustDeprecatedDuration("api-webhook-retry-min-wait", "webhook-retry-max-wait")
|
||||||
w.clientTimeout = flags.MustDuration("webhook-client-timeout")
|
w.clientTimeout = flags.MustDuration("webhook-client-timeout")
|
||||||
w.disable = flags.MustBool("webhook-disable")
|
w.disable = flags.MustDeprecatedBool("api-disable-webhook", "webhook-disable")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestWebhook_Descriptor(t *testing.T) {
|
func TestWebhook_Descriptor(t *testing.T) {
|
||||||
|
|||||||
@@ -2,16 +2,16 @@ package standard
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
// Standard Gotenberg modules.
|
// Standard Gotenberg modules.
|
||||||
_ "github.com/gotenberg/gotenberg/v8/pkg/modules/api"
|
_ "github.com/gotenberg/gotenberg/v7/pkg/modules/api"
|
||||||
_ "github.com/gotenberg/gotenberg/v8/pkg/modules/chromium"
|
_ "github.com/gotenberg/gotenberg/v7/pkg/modules/chromium"
|
||||||
_ "github.com/gotenberg/gotenberg/v8/pkg/modules/libreoffice"
|
_ "github.com/gotenberg/gotenberg/v7/pkg/modules/libreoffice"
|
||||||
_ "github.com/gotenberg/gotenberg/v8/pkg/modules/libreoffice/api"
|
_ "github.com/gotenberg/gotenberg/v7/pkg/modules/libreoffice/api"
|
||||||
_ "github.com/gotenberg/gotenberg/v8/pkg/modules/libreoffice/pdfengine"
|
_ "github.com/gotenberg/gotenberg/v7/pkg/modules/libreoffice/pdfengine"
|
||||||
_ "github.com/gotenberg/gotenberg/v8/pkg/modules/logging"
|
_ "github.com/gotenberg/gotenberg/v7/pkg/modules/logging"
|
||||||
_ "github.com/gotenberg/gotenberg/v8/pkg/modules/pdfcpu"
|
_ "github.com/gotenberg/gotenberg/v7/pkg/modules/pdfcpu"
|
||||||
_ "github.com/gotenberg/gotenberg/v8/pkg/modules/pdfengines"
|
_ "github.com/gotenberg/gotenberg/v7/pkg/modules/pdfengines"
|
||||||
_ "github.com/gotenberg/gotenberg/v8/pkg/modules/pdftk"
|
_ "github.com/gotenberg/gotenberg/v7/pkg/modules/pdftk"
|
||||||
_ "github.com/gotenberg/gotenberg/v8/pkg/modules/prometheus"
|
_ "github.com/gotenberg/gotenberg/v7/pkg/modules/prometheus"
|
||||||
_ "github.com/gotenberg/gotenberg/v8/pkg/modules/qpdf"
|
_ "github.com/gotenberg/gotenberg/v7/pkg/modules/qpdf"
|
||||||
_ "github.com/gotenberg/gotenberg/v8/pkg/modules/webhook"
|
_ "github.com/gotenberg/gotenberg/v7/pkg/modules/webhook"
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user