Compare commits

..

55 Commits
6.1.2 ... 6.4.1

Author SHA1 Message Date
Julien Neuhart
1a9c9a7767 Merge pull request #273 from owsy/pdftk
Upgrade PDFtk
2021-01-07 18:05:36 +01:00
Rhodri Pugh
eb7ce7cef1 use pdftk 3.2.2 2021-01-07 16:17:24 +00:00
Julien Neuhart
68fd8dcd76 Merge pull request #274 from thecodingmachine/github-actions
GitHub Actions
2021-01-07 16:31:54 +01:00
Julien Neuhart
ce8bb2567c Fix typo in publish script 2021-01-07 16:19:21 +01:00
Julien Neuhart
ab3d9bc120 Remove TravisCI, add GitHub Actions 2021-01-07 16:18:48 +01:00
Julien Neuhart
432ec161c1 Add warning in documentation about public facing Gotenberg instances 2020-12-30 15:47:54 +01:00
Julien Neuhart
fcaf4a9b51 Merge pull request #260 from karakanb/add-openapi-docs
Add the first version of the OpenAPI documentation
2020-11-30 15:04:06 +01:00
Burak Karakan
cbb7e6599b Add OpenAPI docs to the links section 2020-11-22 19:59:41 +01:00
Burak Karakan
8f89a144ca Add the first version of the OpenAPI documentation 2020-11-22 19:58:34 +01:00
Julien Neuhart
50bd05b351 Update 15-links.md 2020-11-18 17:30:38 +01:00
Julien Neuhart
4e7f5401ec Merge pull request #241 from Tealsky/bugfix/issue-172
[bugfix/issue-172] fixing health check
2020-09-21 15:38:18 +02:00
Ophélie Volga
3fd56e8c36 [bugfix/issue-172] fixing health check 2020-09-21 14:57:16 +02:00
Julien Neuhart
daf028ab2b Merge pull request #208 from thecodingmachine/6.3.0-dev
6.3.0
2020-06-22 10:58:01 +02:00
Julien Neuhart
5467620b32 adding warning about URL conversion to a Docker Compose service named app 2020-06-09 16:38:28 +02:00
Julien Neuhart
a3cae0f094 Merge branch '6.3.0-dev' of https://github.com/thecodingmachine/gotenberg into 6.3.0-dev 2020-06-09 16:30:22 +02:00
Julien Neuhart
f74eea4314 Merge pull request #204 from WillyReyno/patch-1
Added warning for URLRequest and service named app
2020-06-09 16:30:05 +02:00
Julien Neuhart
04e930a766 fixing typos in documentation 2020-06-09 16:29:14 +02:00
Julien Neuhart
89ac13e805 Fixing linter issues 2020-06-08 10:44:46 +02:00
Julien Neuhart
0b86d38517 go fmt and regenerating documentation 2020-06-08 09:44:37 +02:00
Julien Neuhart
7e76809860 Merge pull request #206 from tomjvdberg/URL-allow-insecure-connection
Url allow insecure connection
2020-06-08 09:28:38 +02:00
tomjvdberg
4d46b98e7f Apply suggestions from code review
Co-authored-by: Julien Neuhart <j.neuhart@thecodingmachine.com>
2020-06-08 08:31:57 +02:00
Julien Neuhart
6c1a46aee5 minor update of the documentation 2020-06-05 15:56:07 +02:00
Julien Neuhart
a7cef5a1c6 got fmt 2020-06-05 15:05:51 +02:00
Julien Neuhart
8a218a6626 using a (more) random directory name for user profile tmp dir + improving performance with a go routine 2020-06-05 15:05:27 +02:00
Julien Neuhart
542fe18dcd avoir directory traversal when uploading a file 2020-06-05 12:06:01 +02:00
Julien Neuhart
25f18ac951 fixing dependencies with master 2020-06-05 12:03:16 +02:00
Julien Neuhart
5540438595 adding missing comment from previous commit 2020-06-05 12:00:57 +02:00
Julien Neuhart
b25456835a sanitizing filename parameter of toHTML function 2020-06-05 11:52:17 +02:00
Julien Neuhart
f86091382f updating dependencies 2020-06-05 09:19:31 +02:00
Julien Neuhart
dbdf8871cc tini no more writeable by gotenberg user 2020-06-05 09:19:12 +02:00
Tom van den Berg
1e6926ee2d Add ignoreCertificateErrors tests + docs 2020-06-02 16:06:17 +02:00
Tom van den Berg
24cbe98ac2 Add feature to have chrome ignore certificate errors when printing using an URL. 2020-06-01 15:31:36 +02:00
Willy Reyno
c590b026d6 Added warning for URLRequest and service named app
A docker-compose service named "app" will systematically return a blank PDF when using `URLRequest()`.
Added a warning to recommend people to rename their service if it's named "app".
2020-06-01 11:15:55 +02:00
Julien Neuhart
d28221aec9 Merge pull request #196 from thecodingmachine/dependabot/go_modules/github.com/sirupsen/logrus-1.6.0
Bump github.com/sirupsen/logrus from 1.5.0 to 1.6.0
2020-05-04 08:32:27 +02:00
dependabot-preview[bot]
df0c6c8c2d Bump github.com/sirupsen/logrus from 1.5.0 to 1.6.0
Bumps [github.com/sirupsen/logrus](https://github.com/sirupsen/logrus) from 1.5.0 to 1.6.0.
- [Release notes](https://github.com/sirupsen/logrus/releases)
- [Changelog](https://github.com/sirupsen/logrus/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sirupsen/logrus/compare/v1.5.0...v1.6.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-04 05:06:23 +00:00
Julien Neuhart
876f0f9a14 Improving error message when the removing of a user profile directory fails 2020-05-01 15:14:42 +02:00
Julien Neuhart
f9f0fc1d67 Removing user profile directory when an Office conversion is over 2020-05-01 15:12:51 +02:00
Julien Neuhart
42e357341a Updating dependencies and fixing lint issues 2020-05-01 14:22:42 +02:00
Julien Neuhart
edc5402aad Adding new badge for Docker pulls in README 2020-05-01 14:22:10 +02:00
Julien Neuhart
3f61ffa6fb Updating CONTRIBUTING.md (Go version) + Rebuilding documentation 2020-05-01 13:08:53 +02:00
Julien Neuhart
1c22b889fe Merge branch 'master' of https://github.com/thecodingmachine/gotenberg 2020-05-01 12:58:15 +02:00
Julien Neuhart
e80f5939b2 Updating markdown files according to some users PRs 2020-05-01 12:58:11 +02:00
Julien Neuhart
c068088cc1 Merge pull request #182 from sdawka/patch-1
Update index.html
2020-05-01 12:53:07 +02:00
Julien Neuhart
1767f371f7 Merge branch 'master' of https://github.com/thecodingmachine/gotenberg 2020-05-01 12:52:14 +02:00
Julien Neuhart
5d5caf64fa Merge pull request #148 from thecodingmachine/bugfix/typo-in-readme
Fixing documentation typo
2020-05-01 12:52:06 +02:00
Julien Neuhart
e095b9419a Updating go version 2020-05-01 12:48:48 +02:00
Sahil Dawka
a5ef072f56 Update index.html 2020-03-18 17:26:22 -04:00
Julien Neuhart
5f6a2456d4 Merge pull request #177 from thecodingmachine/dependabot/go_modules/github.com/stretchr/testify-1.5.1
Bump github.com/stretchr/testify from 1.4.0 to 1.5.1
2020-03-06 14:01:58 +01:00
dependabot-preview[bot]
b0f24889f4 Bump github.com/stretchr/testify from 1.4.0 to 1.5.1
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.4.0 to 1.5.1.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.4.0...v1.5.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-06 08:44:44 +00:00
Julien Neuhart
0a69f0fd51 Merge pull request #176 from pgrimaud/master
Fix typo in README.md
2020-03-05 14:47:53 +01:00
Pierre Grimaud
43c0142682 Fix typo in README.md 2020-03-05 14:43:34 +01:00
Julien Neuhart
1ff16cc54b minor refactoring: using default scale from default options 2020-02-10 10:10:49 +01:00
Julien Neuhart
3609cf54c0 Merge pull request #168 from mdjward/feature/chrome-scaling-support
Add support for Chrome scaling.
2020-02-10 10:03:24 +01:00
Matt Ward
198038f5ea Add support for Chrome scaling. 2020-02-01 19:10:40 +00:00
David Négrier
9e9b369dc2 Fixing documentation typo
s/others/other/g
2019-11-20 19:03:17 +01:00
41 changed files with 1221 additions and 475 deletions

View File

@@ -8,7 +8,7 @@ find below useful information about how to contribute to the Gotenberg project.
### Install from sources
1. Install and run the latest version of Docker
2. Verify your Go version (>= 1.13)
2. Verify your Go version (>= 1.14)
3. Fork this repository
4. Clone it outside of your `GOPATH` (we're using Go modules)

View File

@@ -1,14 +0,0 @@
name: Publish
on:
push:
branches:
- releases/*
jobs:
publish:
name: Publish to Docker Hub
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v1
- run: echo $GITHUB_REF
#- run: make publish VERSION=$GITHUB_REF DOCKER_USER=${{ secrets.DOCKER_USER }} DOCKER_PASSWORD=${{ secrets.DOCKER_PASSWORD }}

View File

@@ -1,12 +0,0 @@
name: Push
on: push
jobs:
tests:
name: Lint, tests and code coverage
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v1
#- run: make lint
#- run: make tests CODE_COVERAGE=1
#- run: bash <(curl -s https://codecov.io/bash) -t ${{ secrets.CODECOV_TOKEN }}

View File

@@ -0,0 +1,26 @@
name: tests_maybe_release
on:
push:
pull_request:
release:
types: [published]
jobs:
tests_maybe_release:
runs-on: ubuntu-latest
steps:
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- uses: actions/checkout@v1
- name: Lint code
run: make lint
- name: Run tests
run: |
make tests CODE_COVERAGE=1
bash <(curl -s https://codecov.io/bash)
- name: Release
if: ${{ github.event_name == 'release' }}
run: make publish VERSION=${{ github.event.release.tag_name }} DOCKER_USER=${{ secrets.DOCKER_USER }} DOCKER_PASSWORD=${{ secrets.DOCKER_PASSWORD }}

View File

@@ -1,26 +0,0 @@
sudo: required
language: generic
services:
- docker
env:
global:
- secure: d6pliM7iR7KJ3TQkr1C0tHi0HgU6Vdmc/ycw11XrXs794/dgj6g9UDrhgcvT2mTzOVIUNouHx7gyEWAlwJv7p8PPyguHJfIsyqMWfaXNPoytTx1ETUiygl1G/UsvXVdry6I5tZF/U+/PJ3hBXoAvo8EOFe0/MxxvP3ghCeeCpeEwEdoYP2jbADGffywVUNI7OEdmS29A28GusXXp2cWCIZYSq5Gn5iu1ZMPaJA6SkRTXpS+iVVLUvlbz5HW09ETFfurTvsdz5Ze4JICiuQt///CR1uwdxD8K7Ils/mkenaMtja9NHA9RdQBa51Azn8iCLsObCTYc52Zb6atbCi/X4ae9M8GA8qaaLigNz5G4v1BHneJylYfWKj5GwqUA3VmRu1mlEw7d2sN2uFRmgIl5kgsYYyOVOLBOKaJWhyBhNnR4U8KeqwUkOnlSdmNZnjfexXXBEcKHen1SozpXwMnzZh8LZuN6BNYd6TVpKmjulGI3vqNMM0c4JJZ9pSm4q5FjbNGQArwCsujDDwY4bCGZSJJTPMXjYOrx4yJ1RY+0TpyV9oDU3ogEqaWnhrkgrphfiYBlDY2bEvrxMTMukLCxJb0Nru6LGCpg4X1P9hu9r0gceqUSnBgdJI4rYTNddenxlEd4j/Thqv835stJWz6YoV+rfvERuSUpntl8xvrk3U8=
- secure: CmLh169xwt3OC7l19lDUwdZYfw+l/+XXKjTGxnuJiXHa9hAH+nc4mEUSmqHMHwjquyYE6LnSVGZ94LH8sCaGed/TMm0Lj3DZtZ0VsjYu3sKsNrVJn5WdYEah+GvNJ8i8uzvHnGk1L2fAagLpXFUB2K9UraA1CFiHs1fF5Fua7Ra0mE7lMz0MFvyvzcmc9u8nMz71K2YVsXqn+I/rv8onJCTtc/Ds0hVrBXwG5Fh0T3xHqjOhME9ZI2hRHnnQVdBojSY3USifQ//qAKowfBVc0pebEtgFpyXx/I17A6xCm0KVtnA2tNqu1+XFkjWaBzryaLE9ujhJcTRC/YK0JUZ+QxfYbD5C7jVy9Jlx1lOEjJ42B/ImNxPP+9p3wi2MWNbcbZJPxIe635fnT17lI7CLK2Qg1xjfTQ4WTEKW5IhnYMiyfRKiripFh4PCkDm8cJlTbpntsX1n0uheydbsgf/11vS7o1WGhVQ8+Jelc4DvIbBHCqienQEOLJDBwm7FiK5JbwZJ6ZAGeDM/IhGU1h9PNjVbHUyJhUxYoPYUE9QsZFT7ENCrRQpq49KOzXBRfffyJNJRh+Ep9X5YoW0dUp0MNu5B7z2+6w9X9n/C+loa11xF42bFzniCvvbSOQ37J+EngDinVxJkNPeyivR4fye2ENhIAjAEjouTJLOSXL9nAOU=
stages:
- tests
- publish
jobs:
include:
- stage: tests
script:
- make lint
- make tests CODE_COVERAGE=1
- bash <(curl -s https://codecov.io/bash)
- stage: publish
if: tag IS present
script: make publish VERSION=$TRAVIS_TAG DOCKER_USER=$DOCKER_USER DOCKER_PASSWORD=$DOCKER_PASS

View File

@@ -1,13 +1,13 @@
GOLANG_VERSION=1.13
GOLANG_VERSION=1.14
VERSION=snapshot
DOCKER_USER=
DOCKER_PASSWORD=
DOCKER_REGISTRY=thecodingmachine
GOTENBERG_USER_GID=1001
GOTENBERG_USER_UID=1001
GOLANGCI_LINT_VERSION=1.20.1
GOLANGCI_LINT_VERSION=1.27.0
CODE_COVERAGE=0
TINI_VERSION=0.18.0
TINI_VERSION=0.19.0
MAXIMUM_WAIT_TIMEOUT=30.0
MAXIMUM_WAIT_DELAY=10.0
MAXIMUM_WEBHOOK_URL_TIMEOUT=30.0
@@ -19,6 +19,7 @@ DISABLE_UNOCONV=0
LOG_LEVEL=INFO
ROOT_PATH=/
DEFAULT_GOOGLE_CHROME_RPCC_BUFFER_SIZE=1048576
GOOGLE_CHROME_IGNORE_CERTIFICATE_ERRORS=0
# build the base Docker image.
base:
@@ -36,7 +37,6 @@ fmt:
# run all linters.
lint:
make workspace
docker build --build-arg GOLANGCI_LINT_VERSION=$(GOLANGCI_LINT_VERSION) -t $(DOCKER_REGISTRY)/gotenberg:lint -f build/lint/Dockerfile .
docker run --rm $(DOCKER_REGISTRY)/gotenberg:lint
@@ -58,7 +58,7 @@ image:
# start the API using previously built Docker image.
gotenberg:
docker run -it --rm -e MAXIMUM_WAIT_TIMEOUT=$(MAXIMUM_WAIT_TIMEOUT) -e MAXIMUM_WAIT_DELAY=$(MAXIMUM_WAIT_DELAY) -e MAXIMUM_WEBHOOK_URL_TIMEOUT=$(MAXIMUM_WEBHOOK_URL_TIMEOUT) -e DEFAULT_WEBHOOK_URL_TIMEOUT=$(DEFAULT_WEBHOOK_URL_TIMEOUT) -e MAXIMUM_WEBHOOK_URL_TIMEOUT=$(MAXIMUM_WEBHOOK_URL_TIMEOUT) -e DEFAULT_LISTEN_PORT=$(DEFAULT_LISTEN_PORT) -e DISABLE_GOOGLE_CHROME=$(DISABLE_GOOGLE_CHROME) -e DISABLE_UNOCONV=$(DISABLE_UNOCONV) -e LOG_LEVEL=$(LOG_LEVEL) -e ROOT_PATH=$(ROOT_PATH) -e DEFAULT_GOOGLE_CHROME_RPCC_BUFFER_SIZE=$(DEFAULT_GOOGLE_CHROME_RPCC_BUFFER_SIZE) -p "$(DEFAULT_LISTEN_PORT):$(DEFAULT_LISTEN_PORT)" $(DOCKER_REGISTRY)/gotenberg:$(VERSION)
docker run -it --rm -e MAXIMUM_WAIT_TIMEOUT=$(MAXIMUM_WAIT_TIMEOUT) -e MAXIMUM_WAIT_DELAY=$(MAXIMUM_WAIT_DELAY) -e MAXIMUM_WEBHOOK_URL_TIMEOUT=$(MAXIMUM_WEBHOOK_URL_TIMEOUT) -e DEFAULT_WEBHOOK_URL_TIMEOUT=$(DEFAULT_WEBHOOK_URL_TIMEOUT) -e MAXIMUM_WEBHOOK_URL_TIMEOUT=$(MAXIMUM_WEBHOOK_URL_TIMEOUT) -e DEFAULT_LISTEN_PORT=$(DEFAULT_LISTEN_PORT) -e DISABLE_GOOGLE_CHROME=$(DISABLE_GOOGLE_CHROME) -e DISABLE_UNOCONV=$(DISABLE_UNOCONV) -e LOG_LEVEL=$(LOG_LEVEL) -e ROOT_PATH=$(ROOT_PATH) -e DEFAULT_GOOGLE_CHROME_RPCC_BUFFER_SIZE=$(DEFAULT_GOOGLE_CHROME_RPCC_BUFFER_SIZE) -e GOOGLE_CHROME_IGNORE_CERTIFICATE_ERRORS=$(GOOGLE_CHROME_IGNORE_CERTIFICATE_ERRORS) -p "$(DEFAULT_LISTEN_PORT):$(DEFAULT_LISTEN_PORT)" $(DOCKER_REGISTRY)/gotenberg:$(VERSION)
# publish Gotenberg images according to version.
publish:

View File

@@ -13,7 +13,7 @@ At TheCodingMachine, we build a lot of web applications (intranets, extranets an
* HTML and Markdown conversions using Google Chrome headless
* Office conversions (.txt, .rtf, .docx, .doc, .odt, .pptx, .ppt, .odp and so on) using [unoconv](https://github.com/dagwieers/unoconv)
* Assets :package:: send your header, footer, images, fonts, stylesheets and so on for converting your HTML and Markdown to beaufitul PDFs!
* Assets :package:: send your header, footer, images, fonts, stylesheets and so on for converting your HTML and Markdown to beautiful PDFs!
* Easily interact with the API using our [Go](https://github.com/thecodingmachine/gotenberg-go-client) and [PHP](https://github.com/thecodingmachine/gotenberg-php-client) libraries
## Quick start
@@ -31,8 +31,9 @@ to learn how to interact with it!
## Badges
[![Docker pulls](https://img.shields.io/docker/pulls/thecodingmachine/gotenberg)](https://hub.docker.com/r/thecodingmachine/gotenberg)
[![Docker image layers](https://images.microbadger.com/badges/image/thecodingmachine/gotenberg:6.svg)](https://microbadger.com/images/thecodingmachine/gotenberg:6)
[![Travis CI](https://travis-ci.org/thecodingmachine/gotenberg.svg?branch=master)](https://travis-ci.org/thecodingmachine/gotenberg)
[![GitHub Actions](https://github.com/thecodingmachine/gotenberg/workflows/tests_maybe_release/badge.svg)](https://github.com/thecodingmachine/gotenberg/workflows/tests_maybe_release)
[![GoDoc](https://godoc.org/github.com/thecodingmachine/gotenberg?status.svg)](https://godoc.org/github.com/thecodingmachine/gotenberg)
[![Codecov](https://codecov.io/gh/thecodingmachine/gotenberg/branch/master/graph/badge.svg)](https://codecov.io/gh/thecodingmachine/gotenberg)
[![Go Report Card](https://goreportcard.com/badge/github.com/thecodingmachine/gotenberg)](https://goreportcard.com/report/thecodingmachine/gotenberg)

View File

@@ -58,7 +58,10 @@ RUN curl -Ls $UNO_URL -o /usr/bin/unoconv &&\
# | https://github.com/thecodingmachine/gotenberg/issues/29
# |
RUN apt-get -y install pdftk
ARG PDFTK_VERSION=924565150
RUN wget -O /usr/bin/pdftk "https://gitlab.com/pdftk-java/pdftk/-/jobs/${PDFTK_VERSION}/artifacts/raw/build/native-image/pdftk" \
&& chmod a+x /usr/bin/pdftk
# |--------------------------------------------------------------------------
# | Fonts

View File

@@ -4,6 +4,8 @@ title: Install
Gotenberg is shipped within a Docker image.
**[Gotenberg should ONLY be used in a trusted network by trusted applications. Do NOT expose Gotenberg to the external world.](https://blazej-adamczyk.medium.com/0-day-bug-breaks-multi-million-dollar-system-38c9e31b27e9)**
You may start it with:
```bash
@@ -24,7 +26,7 @@ For instance:
```bash
$ git clone https://github.com/thecodingmachine/gotenberg.git
$ make publish GOTENBERG_USER_GID=your_custom_gid GOTENBERG_USER_UID=your_custom_uid DOCKER_REGISTRY=your_registry DOCKER_USER=registry_user DOCKER_PASSWORD=registry_password VERSION=6.1.0
$ make publish GOTENBERG_USER_GID=your_custom_gid GOTENBERG_USER_UID=your_custom_uid DOCKER_REGISTRY=your_registry DOCKER_USER=registry_user DOCKER_PASSWORD=registry_password VERSION=version
```
> `master` branch is always up-to-date with the latest version of the API.
@@ -38,7 +40,7 @@ version: '3'
services:
# your others services
# your other services
gotenberg:
image: thecodingmachine/gotenberg:6
@@ -65,6 +67,7 @@ securityContext:
## Cloud Run (Google Cloud)
If you're looking for cost savings, you might be interested by [Cloud Run](https://cloud.google.com/run).
However, according to some users, doing asynchronous conversion (with a webhook) might not working.
In the following examples, we will assume your
Gotenberg API is available at [http://localhost:3000](http://localhost:3000).

View File

@@ -58,6 +58,14 @@ The hard limit is 100 MB and is defined by Google Chrome itself.
> The default Google Chrome rpcc buffer size may also be overridden per request thanks to the form field `googleChromeRpccBufferSize`.
> See the [rpcc buffer size section](#html.rpcc_buffer_size).
## Google Chrome ignore certificate errors
When performing a [URL](#url) conversion, Google Chrome will not accept certificate errors.
You may allow insecure connections by setting the `GOOGLE_CHROME_IGNORE_CERTIFICATE_ERRORS` environment variable to `"1"`.
**You should be careful with this feature and only enable it in your development environment.**
## Disable LibreOffice (unoconv)
You may also disable LibreOffice (unoconv) with `DISABLE_UNOCONV`.

View File

@@ -157,7 +157,7 @@ You may also send additional files. For instance: images, fonts, stylesheets and
The only requirement is to make sure that their paths
are on the same level as the `index.html` file.
In others words, this will work:
In other words, this will work:
```html
<!doctype html>
@@ -243,11 +243,11 @@ $dest = 'result.pdf';
$client->store($request, $dest);
```
## Paper size, margins, orientation
## Paper size, margins, orientation, scaling
You may also customize the resulting PDF format.
By default, it will be rendered with `A4` size, `1 inch` margins and `portrait` orientation.
By default, it will be rendered with `A4` size, `1 inch` margins and `portrait` orientation and 100% (`1.0`) page scale.
> Paper size and margins have to be provided in `inches`. Same for margins.
@@ -265,6 +265,7 @@ $ curl --request POST \
--form marginLeft=0 \
--form marginRight=0 \
--form landscape=true \
--form scale=0.75 \
-o result.pdf
```
@@ -279,6 +280,7 @@ req := gotenberg.NewHTMLRequest(index)
req.PaperSize(gotenberg.A4)
req.Margins(gotenberg.NoMargins)
req.Landscape(true)
req.Scale(0.75)
dest := "result.pdf"
c.Store(req, dest)
```
@@ -297,6 +299,7 @@ $request = new HTMLRequest($index);
$request->setPaperSize(Request::A4);
$request->setMargins(Request::NO_MARGINS);
$request->setLandscape(true);
$request->setScale(0.75);
$dest = 'result.pdf';
$client->store($request, $dest);
```

View File

@@ -14,6 +14,9 @@ named `remoteURL` instead. Otherwise, URL conversions work the same as HTML conv
> **Attention:** when converting a website to PDF, you should remove all margins.
> If not, some of the content of the page might be hidden.
> **Attention:** if you try to convert a URL from a Docker Compose service named `app` (i.e. `removeURL` = `http://app/an/entrypoint`),
> the resulting PDF will be blank. Make sure to rename your service to avoid this issue.
### cURL
```bash

View File

@@ -37,7 +37,7 @@ version: '3'
services:
# your others services
# your other services
gotenberg:
image: thecodingmachine/gotenberg:6

View File

@@ -8,4 +8,5 @@ title: Links
* Follow the progress on the [GitHub repository](https://github.com/thecodingmachine/gotenberg)
* Follow [@gulnap](https://twitter.com/gulnap) on Twitter
* Thanks to [@mafredri](https://github.com/mafredri) for its help and its wonderful [cdp](https://github.com/mafredri/cdp) library
* Thanks to [@mafredri](https://github.com/mafredri) for his help and his wonderful [cdp](https://github.com/mafredri/cdp) library
* OpenAPI documentation for the API can be found [on GitHub](https://github.com/thecodingmachine/gotenberg/blob/master/docs/openapi.yaml)

View File

@@ -1,17 +1,6 @@
FROM thecodingmachine/gotenberg:workspace
# |--------------------------------------------------------------------------
# | GolangCI-Lint
# |--------------------------------------------------------------------------
# |
# | Installs GolangCI-Lint, a linters Runner for Go. 5x faster
# | than gometalinter.
# |
ARG GOLANGCI_LINT_VERSION
RUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b /usr/local/bin v${GOLANGCI_LINT_VERSION} &&\
golangci-lint --version
FROM golangci/golangci-lint:v$GOLANGCI_LINT_VERSION
# |--------------------------------------------------------------------------
# | Final touch
@@ -21,16 +10,13 @@ RUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.s
# |
# Define our working directory outside of $GOPATH (we're using go modules).
USER gotenberg
WORKDIR /gotenberg/lint
# Install module dependencies.
COPY --chown=gotenberg:gotenberg go.mod go.sum ./
# Copy our code source.
COPY . .
# Install module dependencies.
RUN go mod download &&\
go mod verify
# Copy our code source.
COPY --chown=gotenberg:gotenberg . .
CMD ["golangci-lint", "run" ,"--tests=false", "--enable-all", "--disable=dupl", "--disable=funlen", "--disable=wsl", "--disable=gocognit" ]
CMD ["golangci-lint", "run" ,"--tests=false", "--enable-all", "--disable=dupl", "--disable=funlen", "--disable=wsl", "--disable=gocognit", "--disable=gomnd", "--disable=goimports" ]

View File

@@ -10,7 +10,7 @@ FROM thecodingmachine/gotenberg:workspace AS workspace
ARG VERSION
ENV GOOS=linux \
GOARCH=amd64 \
GOARCH=amd64 \
CGO_ENABLED=0
# Define our workding outside of $GOPATH (we're using go modules).
@@ -42,7 +42,7 @@ LABEL authors="Julien Neuhart <j.neuhart@thecodingmachine.com>"
ARG TINI_VERSION
ADD --chown=gotenberg https://github.com/krallin/tini/releases/download/v${TINI_VERSION}/tini-static /tini
ADD https://github.com/krallin/tini/releases/download/v${TINI_VERSION}/tini-static /tini
RUN chmod +x /tini
ENTRYPOINT [ "/tini", "--" ]

View File

@@ -1,6 +1,7 @@
package main
import (
"errors"
"fmt"
"net/http"
"os"
@@ -24,11 +25,11 @@ func main() {
if err != nil {
systemLogger.FatalOp(op, err)
}
systemLogger.InfofOp(op, "Gotenberg %s", version)
systemLogger.DebugfOp(op, "configuration: %+v", config)
systemLogger.InfoOpf(op, "Gotenberg %s", version)
systemLogger.DebugOpf(op, "configuration: %+v", config)
if !config.DisableGoogleChrome() {
// start Google Chrome headless.
if err := chrome.Start(systemLogger); err != nil {
if err := chrome.Start(systemLogger, config.GoogleChromeIgnoreCertificateErrors()); err != nil {
systemLogger.FatalOp(op, err)
}
}
@@ -36,9 +37,9 @@ func main() {
srv := xhttp.New(config)
// run our API in a goroutine so that it doesn't block.
go func() {
systemLogger.InfofOp(op, "http server started on port '%d'", config.DefaultListenPort())
systemLogger.InfoOpf(op, "http server started on port '%d'", config.DefaultListenPort())
if err := srv.Start(fmt.Sprintf(":%d", config.DefaultListenPort())); err != nil {
if err != http.ErrServerClosed {
if errors.Is(err, http.ErrServerClosed) {
systemLogger.FatalOp(op, err)
}
}

View File

@@ -132,6 +132,8 @@
</a>Install</h1>
<p>Gotenberg is shipped within a Docker image.</p>
<p><strong><a href="https://blazej-adamczyk.medium.com/0-day-bug-breaks-multi-million-dollar-system-38c9e31b27e9">Gotenberg should ONLY be used in a trusted network by trusted applications. Do NOT expose Gotenberg to the external world.</a></strong></p>
<p>You may start it with:</p>
<pre class="chroma">$ docker run --rm -p 3000:3000 thecodingmachine/gotenberg:6
@@ -154,7 +156,7 @@
<p>For instance:</p>
<pre class="chroma">$ git clone https://github.com/thecodingmachine/gotenberg.git
$ make publish <span class="nv">GOTENBERG_USER_GID</span><span class="o">=</span>your_custom_gid <span class="nv">GOTENBERG_USER_UID</span><span class="o">=</span>your_custom_uid <span class="nv">DOCKER_REGISTRY</span><span class="o">=</span>your_registry <span class="nv">DOCKER_USER</span><span class="o">=</span>registry_user <span class="nv">DOCKER_PASSWORD</span><span class="o">=</span>registry_password <span class="nv">VERSION</span><span class="o">=</span>6.1.0
$ make publish <span class="nv">GOTENBERG_USER_GID</span><span class="o">=</span>your_custom_gid <span class="nv">GOTENBERG_USER_UID</span><span class="o">=</span>your_custom_uid <span class="nv">DOCKER_REGISTRY</span><span class="o">=</span>your_registry <span class="nv">DOCKER_USER</span><span class="o">=</span>registry_user <span class="nv">DOCKER_PASSWORD</span><span class="o">=</span>registry_password <span class="nv">VERSION</span><span class="o">=</span>version
</pre>
<blockquote>
@@ -167,14 +169,14 @@ $ make publish <span class="nv">GOTENBERG_USER_GID</span><span class="o">=</span
<p>You may also add it in your Docker Compose stack:</p>
<pre class="chroma"><span class="k">version</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;3&#39;</span><span class="w">
<pre class="chroma"><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;3&#39;</span><span class="w">
</span><span class="w">
</span><span class="w"></span><span class="k">services</span><span class="p">:</span><span class="w">
</span><span class="w"></span><span class="nt">services</span><span class="p">:</span><span class="w">
</span><span class="w">
</span><span class="w"> </span><span class="c"># your others services</span><span class="w">
</span><span class="w"> </span><span class="c"># your other services</span><span class="w">
</span><span class="w">
</span><span class="w"> </span><span class="k">gotenberg</span><span class="p">:</span><span class="w">
</span><span class="w"> </span><span class="k">image</span><span class="p">:</span><span class="w"> </span>thecodingmachine/gotenberg<span class="p">:</span><span class="m">6</span><span class="w">
</span><span class="w"> </span><span class="nt">gotenberg</span><span class="p">:</span><span class="w">
</span><span class="w"> </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">thecodingmachine/gotenberg:6</span><span class="w">
</span></pre>
<blockquote>
@@ -204,7 +206,8 @@ $ make publish <span class="nv">GOTENBERG_USER_GID</span><span class="o">=</span
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>
</a>Cloud Run (Google Cloud)</h2>
<p>If youre looking for cost savings, you might be interested by <a href="https://cloud.google.com/run">Cloud Run</a>.</p>
<p>If youre looking for cost savings, you might be interested by <a href="https://cloud.google.com/run">Cloud Run</a>.
However, according to some users, doing asynchronous conversion (with a webhook) might not working.</p>
<p>In the following examples, we will assume your
Gotenberg API is available at <a href="http://localhost:3000">http://localhost:3000</a>.</p>
@@ -331,6 +334,16 @@ The hard limit is 100 MB and is defined by Google Chrome itself.</p>
See the <a href="#html.rpcc_buffer_size">rpcc buffer size section</a>.</p>
</blockquote>
<h2 class="Heading"><a class="Anchor" aria-hidden="true" id="environment_variables.google_chrome_ignore_certificate_errors" href="#environment_variables.google_chrome_ignore_certificate_errors">
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>
</a>Google Chrome ignore certificate errors</h2>
<p>When performing a <a href="#url">URL</a> conversion, Google Chrome will not accept certificate errors.</p>
<p>You may allow insecure connections by setting the <code>GOOGLE_CHROME_IGNORE_CERTIFICATE_ERRORS</code> environment variable to <code>&#34;1&#34;</code>.</p>
<p><strong>You should be careful with this feature and only enable it in your development environment.</strong></p>
<h2 class="Heading"><a class="Anchor" aria-hidden="true" id="environment_variables.disable_libre_office_unoconv" href="#environment_variables.disable_libre_office_unoconv">
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>
</a>Disable LibreOffice (unoconv)</h2>
@@ -470,15 +483,15 @@ which will be converted to PDF.</p>
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>
</a>PHP</h3>
<pre class="chroma">use TheCodingMachine\Gotenberg\Client;
use TheCodingMachine\Gotenberg\DocumentFactory;
use TheCodingMachine\Gotenberg\HTMLRequest;
<pre class="chroma"><span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\Client</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\DocumentFactory</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\HTMLRequest</span><span class="p">;</span>
$client = new Client(&#39;http://localhost:3000&#39;, new \Http\Adapter\Guzzle6\Client());
$index = DocumentFactory::makeFromPath(&#39;index.html&#39;, &#39;/path/to/file&#39;);
$request = new HTMLRequest($index);
$dest = &#39;result.pdf&#39;;
$client-&gt;store($request, $dest);
<span class="nv">$client</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Client</span><span class="p">(</span><span class="s1">&#39;http://localhost:3000&#39;</span><span class="p">,</span> <span class="k">new</span> <span class="nx">\Http\Adapter\Guzzle6\Client</span><span class="p">());</span>
<span class="nv">$index</span> <span class="o">=</span> <span class="nx">DocumentFactory</span><span class="o">::</span><span class="na">makeFromPath</span><span class="p">(</span><span class="s1">&#39;index.html&#39;</span><span class="p">,</span> <span class="s1">&#39;/path/to/file&#39;</span><span class="p">);</span>
<span class="nv">$request</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">HTMLRequest</span><span class="p">(</span><span class="nv">$index</span><span class="p">);</span>
<span class="nv">$dest</span> <span class="o">=</span> <span class="s1">&#39;result.pdf&#39;</span><span class="p">;</span>
<span class="nv">$client</span><span class="o">-&gt;</span><span class="na">store</span><span class="p">(</span><span class="nv">$request</span><span class="p">,</span> <span class="nv">$dest</span><span class="p">);</span>
</pre>
<h2 class="Heading"><a class="Anchor" aria-hidden="true" id="html.header_and_footer" href="#html.header_and_footer">
@@ -562,19 +575,19 @@ Respectively, a file named <code>header.html</code> and <code>footer.html</code>
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>
</a>PHP</h3>
<pre class="chroma">use TheCodingMachine\Gotenberg\Client;
use TheCodingMachine\Gotenberg\DocumentFactory;
use TheCodingMachine\Gotenberg\HTMLRequest;
<pre class="chroma"><span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\Client</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\DocumentFactory</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\HTMLRequest</span><span class="p">;</span>
$client = new Client(&#39;http://localhost:3000&#39;, new \Http\Adapter\Guzzle6\Client());
$index = DocumentFactory::makeFromPath(&#39;index.html&#39;, &#39;/path/to/file&#39;);
$header = DocumentFactory::makeFromPath(&#39;header.html&#39;, &#39;/path/to/file&#39;);
$footer = DocumentFactory::makeFromPath(&#39;footer.html&#39;, &#39;/path/to/file&#39;);
$request = new HTMLRequest($index);
$request-&gt;setHeader($header);
$request-&gt;setFooter($footer);
$dest = &#39;result.pdf&#39;;
$client-&gt;store($request, $dest);
<span class="nv">$client</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Client</span><span class="p">(</span><span class="s1">&#39;http://localhost:3000&#39;</span><span class="p">,</span> <span class="k">new</span> <span class="nx">\Http\Adapter\Guzzle6\Client</span><span class="p">());</span>
<span class="nv">$index</span> <span class="o">=</span> <span class="nx">DocumentFactory</span><span class="o">::</span><span class="na">makeFromPath</span><span class="p">(</span><span class="s1">&#39;index.html&#39;</span><span class="p">,</span> <span class="s1">&#39;/path/to/file&#39;</span><span class="p">);</span>
<span class="nv">$header</span> <span class="o">=</span> <span class="nx">DocumentFactory</span><span class="o">::</span><span class="na">makeFromPath</span><span class="p">(</span><span class="s1">&#39;header.html&#39;</span><span class="p">,</span> <span class="s1">&#39;/path/to/file&#39;</span><span class="p">);</span>
<span class="nv">$footer</span> <span class="o">=</span> <span class="nx">DocumentFactory</span><span class="o">::</span><span class="na">makeFromPath</span><span class="p">(</span><span class="s1">&#39;footer.html&#39;</span><span class="p">,</span> <span class="s1">&#39;/path/to/file&#39;</span><span class="p">);</span>
<span class="nv">$request</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">HTMLRequest</span><span class="p">(</span><span class="nv">$index</span><span class="p">);</span>
<span class="nv">$request</span><span class="o">-&gt;</span><span class="na">setHeader</span><span class="p">(</span><span class="nv">$header</span><span class="p">);</span>
<span class="nv">$request</span><span class="o">-&gt;</span><span class="na">setFooter</span><span class="p">(</span><span class="nv">$footer</span><span class="p">);</span>
<span class="nv">$dest</span> <span class="o">=</span> <span class="s1">&#39;result.pdf&#39;</span><span class="p">;</span>
<span class="nv">$client</span><span class="o">-&gt;</span><span class="na">store</span><span class="p">(</span><span class="nv">$request</span><span class="p">,</span> <span class="nv">$dest</span><span class="p">);</span>
</pre>
<h2 class="Heading"><a class="Anchor" aria-hidden="true" id="html.assets" href="#html.assets">
@@ -586,7 +599,7 @@ $client-&gt;store($request, $dest);
<p>The only requirement is to make sure that their paths
are on the same level as the <code>index.html</code> file.</p>
<p>In others words, this will work:</p>
<p>In other words, this will work:</p>
<pre class="chroma"><span class="cp">&lt;!doctype html&gt;</span>
<span class="p">&lt;</span><span class="nt">html</span> <span class="na">lang</span><span class="o">=</span><span class="s">&#34;en&#34;</span><span class="p">&gt;</span>
@@ -658,36 +671,36 @@ see to the <a href="#fonts">fonts section</a>.</p>
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>
</a>PHP</h3>
<pre class="chroma">use TheCodingMachine\Gotenberg\Client;
use TheCodingMachine\Gotenberg\DocumentFactory;
use TheCodingMachine\Gotenberg\HTMLRequest;
<pre class="chroma"><span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\Client</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\DocumentFactory</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\HTMLRequest</span><span class="p">;</span>
$client = new Client(&#39;http://localhost:3000&#39;, new \Http\Adapter\Guzzle6\Client());
$index = DocumentFactory::makeFromPath(&#39;index.html&#39;, &#39;/path/to/file&#39;);
$assets = [
DocumentFactory::makeFromPath(&#39;style.css&#39;, &#39;/path/to/file&#39;),
DocumentFactory::makeFromPath(&#39;img.png&#39;, &#39;/path/to/file&#39;),
DocumentFactory::makeFromPath(&#39;font.woff&#39;, &#39;/path/to/file&#39;),
];
$request = new HTMLRequest($index);
$request-&gt;setAssets($assets);
$dest = &#39;result.pdf&#39;;
$client-&gt;store($request, $dest);
<span class="nv">$client</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Client</span><span class="p">(</span><span class="s1">&#39;http://localhost:3000&#39;</span><span class="p">,</span> <span class="k">new</span> <span class="nx">\Http\Adapter\Guzzle6\Client</span><span class="p">());</span>
<span class="nv">$index</span> <span class="o">=</span> <span class="nx">DocumentFactory</span><span class="o">::</span><span class="na">makeFromPath</span><span class="p">(</span><span class="s1">&#39;index.html&#39;</span><span class="p">,</span> <span class="s1">&#39;/path/to/file&#39;</span><span class="p">);</span>
<span class="nv">$assets</span> <span class="o">=</span> <span class="p">[</span>
<span class="nx">DocumentFactory</span><span class="o">::</span><span class="na">makeFromPath</span><span class="p">(</span><span class="s1">&#39;style.css&#39;</span><span class="p">,</span> <span class="s1">&#39;/path/to/file&#39;</span><span class="p">),</span>
<span class="nx">DocumentFactory</span><span class="o">::</span><span class="na">makeFromPath</span><span class="p">(</span><span class="s1">&#39;img.png&#39;</span><span class="p">,</span> <span class="s1">&#39;/path/to/file&#39;</span><span class="p">),</span>
<span class="nx">DocumentFactory</span><span class="o">::</span><span class="na">makeFromPath</span><span class="p">(</span><span class="s1">&#39;font.woff&#39;</span><span class="p">,</span> <span class="s1">&#39;/path/to/file&#39;</span><span class="p">),</span>
<span class="p">];</span>
<span class="nv">$request</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">HTMLRequest</span><span class="p">(</span><span class="nv">$index</span><span class="p">);</span>
<span class="nv">$request</span><span class="o">-&gt;</span><span class="na">setAssets</span><span class="p">(</span><span class="nv">$assets</span><span class="p">);</span>
<span class="nv">$dest</span> <span class="o">=</span> <span class="s1">&#39;result.pdf&#39;</span><span class="p">;</span>
<span class="nv">$client</span><span class="o">-&gt;</span><span class="na">store</span><span class="p">(</span><span class="nv">$request</span><span class="p">,</span> <span class="nv">$dest</span><span class="p">);</span>
</pre>
<h2 class="Heading"><a class="Anchor" aria-hidden="true" id="html.paper_size_margins_orientation" href="#html.paper_size_margins_orientation">
<h2 class="Heading"><a class="Anchor" aria-hidden="true" id="html.paper_size_margins_orientation_scaling" href="#html.paper_size_margins_orientation_scaling">
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>
</a>Paper size, margins, orientation</h2>
</a>Paper size, margins, orientation, scaling</h2>
<p>You may also customize the resulting PDF format.</p>
<p>By default, it will be rendered with <code>A4</code> size, <code>1 inch</code> margins and <code>portrait</code> orientation.</p>
<p>By default, it will be rendered with <code>A4</code> size, <code>1 inch</code> margins and <code>portrait</code> orientation and 100% (<code>1.0</code>) page scale.</p>
<blockquote>
<p>Paper size and margins have to be provided in <code>inches</code>. Same for margins.</p>
</blockquote>
<h3 class="Heading"><a class="Anchor" aria-hidden="true" id="html.paper_size_margins_orientation.c_url" href="#html.paper_size_margins_orientation.c_url">
<h3 class="Heading"><a class="Anchor" aria-hidden="true" id="html.paper_size_margins_orientation_scaling.c_url" href="#html.paper_size_margins_orientation_scaling.c_url">
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>
</a>cURL</h3>
@@ -702,10 +715,11 @@ $client-&gt;store($request, $dest);
</span><span class="se"></span> --form <span class="nv">marginLeft</span><span class="o">=</span><span class="m">0</span> <span class="se">\
</span><span class="se"></span> --form <span class="nv">marginRight</span><span class="o">=</span><span class="m">0</span> <span class="se">\
</span><span class="se"></span> --form <span class="nv">landscape</span><span class="o">=</span><span class="nb">true</span> <span class="se">\
</span><span class="se"></span> --form <span class="nv">scale</span><span class="o">=</span>0.75 <span class="se">\
</span><span class="se"></span> -o result.pdf
</pre>
<h3 class="Heading"><a class="Anchor" aria-hidden="true" id="html.paper_size_margins_orientation.go" href="#html.paper_size_margins_orientation.go">
<h3 class="Heading"><a class="Anchor" aria-hidden="true" id="html.paper_size_margins_orientation_scaling.go" href="#html.paper_size_margins_orientation_scaling.go">
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>
</a>Go</h3>
@@ -717,27 +731,29 @@ $client-&gt;store($request, $dest);
<span class="nx">req</span><span class="p">.</span><span class="nf">PaperSize</span><span class="p">(</span><span class="nx">gotenberg</span><span class="p">.</span><span class="nx">A4</span><span class="p">)</span>
<span class="nx">req</span><span class="p">.</span><span class="nf">Margins</span><span class="p">(</span><span class="nx">gotenberg</span><span class="p">.</span><span class="nx">NoMargins</span><span class="p">)</span>
<span class="nx">req</span><span class="p">.</span><span class="nf">Landscape</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span>
<span class="nx">req</span><span class="p">.</span><span class="nf">Scale</span><span class="p">(</span><span class="mf">0.75</span><span class="p">)</span>
<span class="nx">dest</span> <span class="o">:=</span> <span class="s">&#34;result.pdf&#34;</span>
<span class="nx">c</span><span class="p">.</span><span class="nf">Store</span><span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">dest</span><span class="p">)</span>
</pre>
<h3 class="Heading"><a class="Anchor" aria-hidden="true" id="html.paper_size_margins_orientation.php" href="#html.paper_size_margins_orientation.php">
<h3 class="Heading"><a class="Anchor" aria-hidden="true" id="html.paper_size_margins_orientation_scaling.php" href="#html.paper_size_margins_orientation_scaling.php">
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>
</a>PHP</h3>
<pre class="chroma">use TheCodingMachine\Gotenberg\Client;
use TheCodingMachine\Gotenberg\DocumentFactory;
use TheCodingMachine\Gotenberg\HTMLRequest;
use TheCodingMachine\Gotenberg\Request;
<pre class="chroma"><span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\Client</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\DocumentFactory</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\HTMLRequest</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\Request</span><span class="p">;</span>
$client = new Client(&#39;http://localhost:3000&#39;, new \Http\Adapter\Guzzle6\Client());
$index = DocumentFactory::makeFromPath(&#39;index.html&#39;, &#39;/path/to/file&#39;);
$request = new HTMLRequest($index);
$request-&gt;setPaperSize(Request::A4);
$request-&gt;setMargins(Request::NO_MARGINS);
$request-&gt;setLandscape(true);
$dest = &#39;result.pdf&#39;;
$client-&gt;store($request, $dest);
<span class="nv">$client</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Client</span><span class="p">(</span><span class="s1">&#39;http://localhost:3000&#39;</span><span class="p">,</span> <span class="k">new</span> <span class="nx">\Http\Adapter\Guzzle6\Client</span><span class="p">());</span>
<span class="nv">$index</span> <span class="o">=</span> <span class="nx">DocumentFactory</span><span class="o">::</span><span class="na">makeFromPath</span><span class="p">(</span><span class="s1">&#39;index.html&#39;</span><span class="p">,</span> <span class="s1">&#39;/path/to/file&#39;</span><span class="p">);</span>
<span class="nv">$request</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">HTMLRequest</span><span class="p">(</span><span class="nv">$index</span><span class="p">);</span>
<span class="nv">$request</span><span class="o">-&gt;</span><span class="na">setPaperSize</span><span class="p">(</span><span class="nx">Request</span><span class="o">::</span><span class="na">A4</span><span class="p">);</span>
<span class="nv">$request</span><span class="o">-&gt;</span><span class="na">setMargins</span><span class="p">(</span><span class="nx">Request</span><span class="o">::</span><span class="na">NO_MARGINS</span><span class="p">);</span>
<span class="nv">$request</span><span class="o">-&gt;</span><span class="na">setLandscape</span><span class="p">(</span><span class="k">true</span><span class="p">);</span>
<span class="nv">$request</span><span class="o">-&gt;</span><span class="na">setScale</span><span class="p">(</span><span class="mf">0.75</span><span class="p">);</span>
<span class="nv">$dest</span> <span class="o">=</span> <span class="s1">&#39;result.pdf&#39;</span><span class="p">;</span>
<span class="nv">$client</span><span class="o">-&gt;</span><span class="na">store</span><span class="p">(</span><span class="nv">$request</span><span class="p">,</span> <span class="nv">$dest</span><span class="p">);</span>
</pre>
<h2 class="Heading"><a class="Anchor" aria-hidden="true" id="html.page_ranges" href="#html.page_ranges">
@@ -779,17 +795,17 @@ of Google Chrome, e.g. <code>1-5,8,11-13</code>.</p>
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>
</a>PHP</h3>
<pre class="chroma">use TheCodingMachine\Gotenberg\Client;
use TheCodingMachine\Gotenberg\DocumentFactory;
use TheCodingMachine\Gotenberg\HTMLRequest;
use TheCodingMachine\Gotenberg\Request;
<pre class="chroma"><span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\Client</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\DocumentFactory</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\HTMLRequest</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\Request</span><span class="p">;</span>
$client = new Client(&#39;http://localhost:3000&#39;, new \Http\Adapter\Guzzle6\Client());
$index = DocumentFactory::makeFromPath(&#39;index.html&#39;, &#39;/path/to/file&#39;);
$request = new HTMLRequest($index);
$request-&gt;setPageRanges(&#39;1-3,5&#39;);
$dest = &#39;result.pdf&#39;;
$client-&gt;store($request, $dest);
<span class="nv">$client</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Client</span><span class="p">(</span><span class="s1">&#39;http://localhost:3000&#39;</span><span class="p">,</span> <span class="k">new</span> <span class="nx">\Http\Adapter\Guzzle6\Client</span><span class="p">());</span>
<span class="nv">$index</span> <span class="o">=</span> <span class="nx">DocumentFactory</span><span class="o">::</span><span class="na">makeFromPath</span><span class="p">(</span><span class="s1">&#39;index.html&#39;</span><span class="p">,</span> <span class="s1">&#39;/path/to/file&#39;</span><span class="p">);</span>
<span class="nv">$request</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">HTMLRequest</span><span class="p">(</span><span class="nv">$index</span><span class="p">);</span>
<span class="nv">$request</span><span class="o">-&gt;</span><span class="na">setPageRanges</span><span class="p">(</span><span class="s1">&#39;1-3,5&#39;</span><span class="p">);</span>
<span class="nv">$dest</span> <span class="o">=</span> <span class="s1">&#39;result.pdf&#39;</span><span class="p">;</span>
<span class="nv">$client</span><span class="o">-&gt;</span><span class="na">store</span><span class="p">(</span><span class="nv">$request</span><span class="p">,</span> <span class="nv">$dest</span><span class="p">);</span>
</pre>
<h2 class="Heading"><a class="Anchor" aria-hidden="true" id="html.wait_delay" href="#html.wait_delay">
@@ -834,17 +850,17 @@ a lot on JavaScript for rendering.</p>
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>
</a>PHP</h3>
<pre class="chroma">use TheCodingMachine\Gotenberg\Client;
use TheCodingMachine\Gotenberg\DocumentFactory;
use TheCodingMachine\Gotenberg\HTMLRequest;
use TheCodingMachine\Gotenberg\Request;
<pre class="chroma"><span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\Client</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\DocumentFactory</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\HTMLRequest</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\Request</span><span class="p">;</span>
$client = new Client(&#39;http://localhost:3000&#39;, new \Http\Adapter\Guzzle6\Client());
$index = DocumentFactory::makeFromPath(&#39;index.html&#39;, &#39;/path/to/file&#39;);
$request = new HTMLRequest($index);
$request-&gt;setWaitDelay(5.5);
$dest = &#39;result.pdf&#39;;
$client-&gt;store($request, $dest);
<span class="nv">$client</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Client</span><span class="p">(</span><span class="s1">&#39;http://localhost:3000&#39;</span><span class="p">,</span> <span class="k">new</span> <span class="nx">\Http\Adapter\Guzzle6\Client</span><span class="p">());</span>
<span class="nv">$index</span> <span class="o">=</span> <span class="nx">DocumentFactory</span><span class="o">::</span><span class="na">makeFromPath</span><span class="p">(</span><span class="s1">&#39;index.html&#39;</span><span class="p">,</span> <span class="s1">&#39;/path/to/file&#39;</span><span class="p">);</span>
<span class="nv">$request</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">HTMLRequest</span><span class="p">(</span><span class="nv">$index</span><span class="p">);</span>
<span class="nv">$request</span><span class="o">-&gt;</span><span class="na">setWaitDelay</span><span class="p">(</span><span class="mf">5.5</span><span class="p">);</span>
<span class="nv">$dest</span> <span class="o">=</span> <span class="s1">&#39;result.pdf&#39;</span><span class="p">;</span>
<span class="nv">$client</span><span class="o">-&gt;</span><span class="na">store</span><span class="p">(</span><span class="nv">$request</span><span class="p">,</span> <span class="nv">$dest</span><span class="p">);</span>
</pre>
<h2 class="Heading"><a class="Anchor" aria-hidden="true" id="html.rpcc_buffer_size" href="#html.rpcc_buffer_size">
@@ -892,17 +908,17 @@ The hard limit is 100 MB and is defined by Google Chrome itself.</p>
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>
</a>PHP</h3>
<pre class="chroma">use TheCodingMachine\Gotenberg\Client;
use TheCodingMachine\Gotenberg\DocumentFactory;
use TheCodingMachine\Gotenberg\HTMLRequest;
use TheCodingMachine\Gotenberg\Request;
<pre class="chroma"><span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\Client</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\DocumentFactory</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\HTMLRequest</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\Request</span><span class="p">;</span>
$client = new Client(&#39;http://localhost:3000&#39;, new \Http\Adapter\Guzzle6\Client());
$index = DocumentFactory::makeFromPath(&#39;index.html&#39;, &#39;/path/to/file&#39;);
$request = new HTMLRequest($index);
$request-&gt;setGoogleChromeRpccBufferSize(1048576);
$dest = &#39;result.pdf&#39;;
$client-&gt;store($request, $dest);
<span class="nv">$client</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Client</span><span class="p">(</span><span class="s1">&#39;http://localhost:3000&#39;</span><span class="p">,</span> <span class="k">new</span> <span class="nx">\Http\Adapter\Guzzle6\Client</span><span class="p">());</span>
<span class="nv">$index</span> <span class="o">=</span> <span class="nx">DocumentFactory</span><span class="o">::</span><span class="na">makeFromPath</span><span class="p">(</span><span class="s1">&#39;index.html&#39;</span><span class="p">,</span> <span class="s1">&#39;/path/to/file&#39;</span><span class="p">);</span>
<span class="nv">$request</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">HTMLRequest</span><span class="p">(</span><span class="nv">$index</span><span class="p">);</span>
<span class="nv">$request</span><span class="o">-&gt;</span><span class="na">setGoogleChromeRpccBufferSize</span><span class="p">(</span><span class="mi">1048576</span><span class="p">);</span>
<span class="nv">$dest</span> <span class="o">=</span> <span class="s1">&#39;result.pdf&#39;</span><span class="p">;</span>
<span class="nv">$client</span><span class="o">-&gt;</span><span class="na">store</span><span class="p">(</span><span class="nv">$request</span><span class="p">,</span> <span class="nv">$dest</span><span class="p">);</span>
</pre>
</div>
@@ -925,6 +941,9 @@ named <code>remoteURL</code> instead. Otherwise, URL conversions work the same a
<blockquote>
<p><strong>Attention:</strong> when converting a website to PDF, you should remove all margins.
If not, some of the content of the page might be hidden.</p>
<p><strong>Attention:</strong> if you try to convert a URL from a Docker Compose service named <code>app</code> (i.e. <code>removeURL</code> = <code>http://app/an/entrypoint</code>),
the resulting PDF will be blank. Make sure to rename your service to avoid this issue.</p>
</blockquote>
<h3 class="Heading"><a class="Anchor" aria-hidden="true" id="url.basic.c_url" href="#url.basic.c_url">
@@ -959,14 +978,14 @@ If not, some of the content of the page might be hidden.</p>
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>
</a>PHP</h3>
<pre class="chroma">use TheCodingMachine\Gotenberg\Client;
use TheCodingMachine\Gotenberg\URLRequest;
<pre class="chroma"><span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\Client</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\URLRequest</span><span class="p">;</span>
$client = new Client(&#39;http://localhost:3000&#39;, new \Http\Adapter\Guzzle6\Client());
$request = new URLRequest(&#39;https://google.com&#39;);
$request-&gt;setMargins(Request::NO_MARGINS);
$dest = &#39;result.pdf&#39;;
$client-&gt;store($request, $dest);
<span class="nv">$client</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Client</span><span class="p">(</span><span class="s1">&#39;http://localhost:3000&#39;</span><span class="p">,</span> <span class="k">new</span> <span class="nx">\Http\Adapter\Guzzle6\Client</span><span class="p">());</span>
<span class="nv">$request</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">URLRequest</span><span class="p">(</span><span class="s1">&#39;https://google.com&#39;</span><span class="p">);</span>
<span class="nv">$request</span><span class="o">-&gt;</span><span class="na">setMargins</span><span class="p">(</span><span class="nx">Request</span><span class="o">::</span><span class="na">NO_MARGINS</span><span class="p">);</span>
<span class="nv">$dest</span> <span class="o">=</span> <span class="s1">&#39;result.pdf&#39;</span><span class="p">;</span>
<span class="nv">$client</span><span class="o">-&gt;</span><span class="na">store</span><span class="p">(</span><span class="nv">$request</span><span class="p">,</span> <span class="nv">$dest</span><span class="p">);</span>
</pre>
<h2 class="Heading"><a class="Anchor" aria-hidden="true" id="url.custom_http_headers" href="#url.custom_http_headers">
@@ -1015,14 +1034,14 @@ canonical key for <code>accept-encoding</code> is <code>Accept-Encoding</code>.<
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>
</a>PHP</h3>
<pre class="chroma">use TheCodingMachine\Gotenberg\Client;
use TheCodingMachine\Gotenberg\URLRequest;
<pre class="chroma"><span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\Client</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\URLRequest</span><span class="p">;</span>
$client = new Client(&#39;http://localhost:3000&#39;, new \Http\Adapter\Guzzle6\Client());
$request = new URLRequest(&#39;https://google.com&#39;);
$request-&gt;addRemoteURLHTTPHeader(&#39;Your-Header&#39;, &#39;Foo&#39;)
$dest = &#39;result.pdf&#39;;
$client-&gt;store($request, $dest);
<span class="nv">$client</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Client</span><span class="p">(</span><span class="s1">&#39;http://localhost:3000&#39;</span><span class="p">,</span> <span class="k">new</span> <span class="nx">\Http\Adapter\Guzzle6\Client</span><span class="p">());</span>
<span class="nv">$request</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">URLRequest</span><span class="p">(</span><span class="s1">&#39;https://google.com&#39;</span><span class="p">);</span>
<span class="nv">$request</span><span class="o">-&gt;</span><span class="na">addRemoteURLHTTPHeader</span><span class="p">(</span><span class="s1">&#39;Your-Header&#39;</span><span class="p">,</span> <span class="s1">&#39;Foo&#39;</span><span class="p">)</span>
<span class="nv">$dest</span> <span class="o">=</span> <span class="s1">&#39;result.pdf&#39;</span><span class="p">;</span>
<span class="nv">$client</span><span class="o">-&gt;</span><span class="na">store</span><span class="p">(</span><span class="nv">$request</span><span class="p">,</span> <span class="nv">$dest</span><span class="p">);</span>
</pre>
</div>
@@ -1088,18 +1107,18 @@ in the file <code>index.html</code>. This function will convert a given markdown
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>
</a>PHP</h3>
<pre class="chroma">use TheCodingMachine\Gotenberg\Client;
use TheCodingMachine\Gotenberg\DocumentFactory;
use TheCodingMachine\Gotenberg\MarkdownRequest;
<pre class="chroma"><span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\Client</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\DocumentFactory</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\MarkdownRequest</span><span class="p">;</span>
$client = new Client(&#39;http://localhost:3000&#39;, new \Http\Adapter\Guzzle6\Client());
$index = DocumentFactory::makeFromPath(&#39;index.html&#39;, &#39;/path/to/file&#39;);
$markdowns = [
DocumentFactory::makeFromPath(&#39;file.md&#39;, &#39;/path/to/file&#39;),
];
$request = new MarkdownRequest($index, $markdowns);
$dest = &#39;result.pdf&#39;;
$client-&gt;store($request, $dest);
<span class="nv">$client</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Client</span><span class="p">(</span><span class="s1">&#39;http://localhost:3000&#39;</span><span class="p">,</span> <span class="k">new</span> <span class="nx">\Http\Adapter\Guzzle6\Client</span><span class="p">());</span>
<span class="nv">$index</span> <span class="o">=</span> <span class="nx">DocumentFactory</span><span class="o">::</span><span class="na">makeFromPath</span><span class="p">(</span><span class="s1">&#39;index.html&#39;</span><span class="p">,</span> <span class="s1">&#39;/path/to/file&#39;</span><span class="p">);</span>
<span class="nv">$markdowns</span> <span class="o">=</span> <span class="p">[</span>
<span class="nx">DocumentFactory</span><span class="o">::</span><span class="na">makeFromPath</span><span class="p">(</span><span class="s1">&#39;file.md&#39;</span><span class="p">,</span> <span class="s1">&#39;/path/to/file&#39;</span><span class="p">),</span>
<span class="p">];</span>
<span class="nv">$request</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">MarkdownRequest</span><span class="p">(</span><span class="nv">$index</span><span class="p">,</span> <span class="nv">$markdowns</span><span class="p">);</span>
<span class="nv">$dest</span> <span class="o">=</span> <span class="s1">&#39;result.pdf&#39;</span><span class="p">;</span>
<span class="nv">$client</span><span class="o">-&gt;</span><span class="na">store</span><span class="p">(</span><span class="nv">$request</span><span class="p">,</span> <span class="nv">$dest</span><span class="p">);</span>
</pre>
</div>
@@ -1169,18 +1188,18 @@ $client-&gt;store($request, $dest);
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>
</a>PHP</h3>
<pre class="chroma">use TheCodingMachine\Gotenberg\Client;
use TheCodingMachine\Gotenberg\DocumentFactory;
use TheCodingMachine\Gotenberg\OfficeRequest;
<pre class="chroma"><span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\Client</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\DocumentFactory</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\OfficeRequest</span><span class="p">;</span>
$client = new Client(&#39;http://localhost:3000&#39;, new \Http\Adapter\Guzzle6\Client());
$files = [
DocumentFactory::makeFromPath(&#39;document.docx&#39;, &#39;/path/to/file&#39;),
DocumentFactory::makeFromPath(&#39;document2.docx&#39;, &#39;/path/to/file&#39;),
];
$request = new OfficeRequest($files);
$dest = &#39;result.pdf&#39;;
$client-&gt;store($request, $dest);
<span class="nv">$client</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Client</span><span class="p">(</span><span class="s1">&#39;http://localhost:3000&#39;</span><span class="p">,</span> <span class="k">new</span> <span class="nx">\Http\Adapter\Guzzle6\Client</span><span class="p">());</span>
<span class="nv">$files</span> <span class="o">=</span> <span class="p">[</span>
<span class="nx">DocumentFactory</span><span class="o">::</span><span class="na">makeFromPath</span><span class="p">(</span><span class="s1">&#39;document.docx&#39;</span><span class="p">,</span> <span class="s1">&#39;/path/to/file&#39;</span><span class="p">),</span>
<span class="nx">DocumentFactory</span><span class="o">::</span><span class="na">makeFromPath</span><span class="p">(</span><span class="s1">&#39;document2.docx&#39;</span><span class="p">,</span> <span class="s1">&#39;/path/to/file&#39;</span><span class="p">),</span>
<span class="p">];</span>
<span class="nv">$request</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">OfficeRequest</span><span class="p">(</span><span class="nv">$files</span><span class="p">);</span>
<span class="nv">$dest</span> <span class="o">=</span> <span class="s1">&#39;result.pdf&#39;</span><span class="p">;</span>
<span class="nv">$client</span><span class="o">-&gt;</span><span class="na">store</span><span class="p">(</span><span class="nv">$request</span><span class="p">,</span> <span class="nv">$dest</span><span class="p">);</span>
</pre>
<h2 class="Heading"><a class="Anchor" aria-hidden="true" id="office.orientation" href="#office.orientation">
@@ -1221,18 +1240,18 @@ $client-&gt;store($request, $dest);
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>
</a>PHP</h3>
<pre class="chroma">use TheCodingMachine\Gotenberg\Client;
use TheCodingMachine\Gotenberg\DocumentFactory;
use TheCodingMachine\Gotenberg\OfficeRequest;
<pre class="chroma"><span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\Client</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\DocumentFactory</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\OfficeRequest</span><span class="p">;</span>
$client = new Client(&#39;http://localhost:3000&#39;, new \Http\Adapter\Guzzle6\Client());
$files = [
DocumentFactory::makeFromPath(&#39;document.docx&#39;, &#39;/path/to/file&#39;),
];
$request = new OfficeRequest($files);
$request-&gt;setLandscape(true);
$dest = &#39;result.pdf&#39;;
$client-&gt;store($request, $dest);
<span class="nv">$client</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Client</span><span class="p">(</span><span class="s1">&#39;http://localhost:3000&#39;</span><span class="p">,</span> <span class="k">new</span> <span class="nx">\Http\Adapter\Guzzle6\Client</span><span class="p">());</span>
<span class="nv">$files</span> <span class="o">=</span> <span class="p">[</span>
<span class="nx">DocumentFactory</span><span class="o">::</span><span class="na">makeFromPath</span><span class="p">(</span><span class="s1">&#39;document.docx&#39;</span><span class="p">,</span> <span class="s1">&#39;/path/to/file&#39;</span><span class="p">),</span>
<span class="p">];</span>
<span class="nv">$request</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">OfficeRequest</span><span class="p">(</span><span class="nv">$files</span><span class="p">);</span>
<span class="nv">$request</span><span class="o">-&gt;</span><span class="na">setLandscape</span><span class="p">(</span><span class="k">true</span><span class="p">);</span>
<span class="nv">$dest</span> <span class="o">=</span> <span class="s1">&#39;result.pdf&#39;</span><span class="p">;</span>
<span class="nv">$client</span><span class="o">-&gt;</span><span class="na">store</span><span class="p">(</span><span class="nv">$request</span><span class="p">,</span> <span class="nv">$dest</span><span class="p">);</span>
</pre>
<h2 class="Heading"><a class="Anchor" aria-hidden="true" id="office.page_ranges" href="#office.page_ranges">
@@ -1279,18 +1298,18 @@ applied for each document.</p>
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>
</a>PHP</h3>
<pre class="chroma">use TheCodingMachine\Gotenberg\Client;
use TheCodingMachine\Gotenberg\DocumentFactory;
use TheCodingMachine\Gotenberg\OfficeRequest;
<pre class="chroma"><span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\Client</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\DocumentFactory</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\OfficeRequest</span><span class="p">;</span>
$client = new Client(&#39;http://localhost:3000&#39;, new \Http\Adapter\Guzzle6\Client());
$files = [
DocumentFactory::makeFromPath(&#39;document.docx&#39;, &#39;/path/to/file&#39;),
];
$request = new OfficeRequest($files);
$request-&gt;setPageRanges(&#39;1-3&#39;);
$dest = &#39;result.pdf&#39;;
$client-&gt;store($request, $dest);
<span class="nv">$client</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Client</span><span class="p">(</span><span class="s1">&#39;http://localhost:3000&#39;</span><span class="p">,</span> <span class="k">new</span> <span class="nx">\Http\Adapter\Guzzle6\Client</span><span class="p">());</span>
<span class="nv">$files</span> <span class="o">=</span> <span class="p">[</span>
<span class="nx">DocumentFactory</span><span class="o">::</span><span class="na">makeFromPath</span><span class="p">(</span><span class="s1">&#39;document.docx&#39;</span><span class="p">,</span> <span class="s1">&#39;/path/to/file&#39;</span><span class="p">),</span>
<span class="p">];</span>
<span class="nv">$request</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">OfficeRequest</span><span class="p">(</span><span class="nv">$files</span><span class="p">);</span>
<span class="nv">$request</span><span class="o">-&gt;</span><span class="na">setPageRanges</span><span class="p">(</span><span class="s1">&#39;1-3&#39;</span><span class="p">);</span>
<span class="nv">$dest</span> <span class="o">=</span> <span class="s1">&#39;result.pdf&#39;</span><span class="p">;</span>
<span class="nv">$client</span><span class="o">-&gt;</span><span class="na">store</span><span class="p">(</span><span class="nv">$request</span><span class="p">,</span> <span class="nv">$dest</span><span class="p">);</span>
</pre>
</div>
@@ -1344,18 +1363,18 @@ will merge them and return the resulting PDF file.</p>
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>
</a>PHP</h3>
<pre class="chroma">use TheCodingMachine\Gotenberg\Client;
use TheCodingMachine\Gotenberg\DocumentFactory;
use TheCodingMachine\Gotenberg\MergeRequest;
<pre class="chroma"><span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\Client</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\DocumentFactory</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\MergeRequest</span><span class="p">;</span>
$client = new Client(&#39;http://localhost:3000&#39;, new \Http\Adapter\Guzzle6\Client());
$files = [
DocumentFactory::makeFromPath(&#39;file.pdf&#39;, &#39;/path/to/file&#39;),
DocumentFactory::makeFromPath(&#39;file2.pdf&#39;, &#39;/path/to/file&#39;),
];
$request = new MergeRequest($files);
$dest = &#39;result.pdf&#39;;
$client-&gt;store($request, $dest);
<span class="nv">$client</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Client</span><span class="p">(</span><span class="s1">&#39;http://localhost:3000&#39;</span><span class="p">,</span> <span class="k">new</span> <span class="nx">\Http\Adapter\Guzzle6\Client</span><span class="p">());</span>
<span class="nv">$files</span> <span class="o">=</span> <span class="p">[</span>
<span class="nx">DocumentFactory</span><span class="o">::</span><span class="na">makeFromPath</span><span class="p">(</span><span class="s1">&#39;file.pdf&#39;</span><span class="p">,</span> <span class="s1">&#39;/path/to/file&#39;</span><span class="p">),</span>
<span class="nx">DocumentFactory</span><span class="o">::</span><span class="na">makeFromPath</span><span class="p">(</span><span class="s1">&#39;file2.pdf&#39;</span><span class="p">,</span> <span class="s1">&#39;/path/to/file&#39;</span><span class="p">),</span>
<span class="p">];</span>
<span class="nv">$request</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">MergeRequest</span><span class="p">(</span><span class="nv">$files</span><span class="p">);</span>
<span class="nv">$dest</span> <span class="o">=</span> <span class="s1">&#39;result.pdf&#39;</span><span class="p">;</span>
<span class="nv">$client</span><span class="o">-&gt;</span><span class="na">store</span><span class="p">(</span><span class="nv">$request</span><span class="p">,</span> <span class="nv">$dest</span><span class="p">);</span>
</pre>
</div>
@@ -1407,17 +1426,17 @@ If unsucessful, it returns a <code>504</code> HTTP code.</p>
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>
</a>PHP</h3>
<pre class="chroma">use TheCodingMachine\Gotenberg\Client;
use TheCodingMachine\Gotenberg\DocumentFactory;
use TheCodingMachine\Gotenberg\HTMLRequest;
use TheCodingMachine\Gotenberg\Request;
<pre class="chroma"><span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\Client</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\DocumentFactory</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\HTMLRequest</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\Request</span><span class="p">;</span>
$client = new Client(&#39;http://localhost:3000&#39;, new \Http\Adapter\Guzzle6\Client());
$index = DocumentFactory::makeFromPath(&#39;index.html&#39;, &#39;/path/to/file&#39;);
$request = new HTMLRequest($index);
$request-&gt;setWaitTimeout(2.5);
$dest = &#39;result.pdf&#39;;
$client-&gt;store($request, $dest);
<span class="nv">$client</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Client</span><span class="p">(</span><span class="s1">&#39;http://localhost:3000&#39;</span><span class="p">,</span> <span class="k">new</span> <span class="nx">\Http\Adapter\Guzzle6\Client</span><span class="p">());</span>
<span class="nv">$index</span> <span class="o">=</span> <span class="nx">DocumentFactory</span><span class="o">::</span><span class="na">makeFromPath</span><span class="p">(</span><span class="s1">&#39;index.html&#39;</span><span class="p">,</span> <span class="s1">&#39;/path/to/file&#39;</span><span class="p">);</span>
<span class="nv">$request</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">HTMLRequest</span><span class="p">(</span><span class="nv">$index</span><span class="p">);</span>
<span class="nv">$request</span><span class="o">-&gt;</span><span class="na">setWaitTimeout</span><span class="p">(</span><span class="mf">2.5</span><span class="p">);</span>
<span class="nv">$dest</span> <span class="o">=</span> <span class="s1">&#39;result.pdf&#39;</span><span class="p">;</span>
<span class="nv">$client</span><span class="o">-&gt;</span><span class="na">store</span><span class="p">(</span><span class="nv">$request</span><span class="p">,</span> <span class="nv">$dest</span><span class="p">);</span>
</pre>
</div>
@@ -1465,15 +1484,15 @@ to given URL.</p>
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>
</a>PHP</h3>
<pre class="chroma">use TheCodingMachine\Gotenberg\Client;
use TheCodingMachine\Gotenberg\DocumentFactory;
use TheCodingMachine\Gotenberg\HTMLRequest;
<pre class="chroma"><span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\Client</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\DocumentFactory</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\HTMLRequest</span><span class="p">;</span>
$client = new Client(&#39;http://localhost:3000&#39;, new \Http\Adapter\Guzzle6\Client());
$index = DocumentFactory::makeFromPath(&#39;index.html&#39;, &#39;/path/to/file&#39;);
$request = new HTMLRequest($index);
$request-&gt;setWebhookURL(&#39;http://myapp.com/webhook/&#39;);
$resp = $client-&gt;post($request);
<span class="nv">$client</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Client</span><span class="p">(</span><span class="s1">&#39;http://localhost:3000&#39;</span><span class="p">,</span> <span class="k">new</span> <span class="nx">\Http\Adapter\Guzzle6\Client</span><span class="p">());</span>
<span class="nv">$index</span> <span class="o">=</span> <span class="nx">DocumentFactory</span><span class="o">::</span><span class="na">makeFromPath</span><span class="p">(</span><span class="s1">&#39;index.html&#39;</span><span class="p">,</span> <span class="s1">&#39;/path/to/file&#39;</span><span class="p">);</span>
<span class="nv">$request</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">HTMLRequest</span><span class="p">(</span><span class="nv">$index</span><span class="p">);</span>
<span class="nv">$request</span><span class="o">-&gt;</span><span class="na">setWebhookURL</span><span class="p">(</span><span class="s1">&#39;http://myapp.com/webhook/&#39;</span><span class="p">);</span>
<span class="nv">$resp</span> <span class="o">=</span> <span class="nv">$client</span><span class="o">-&gt;</span><span class="na">post</span><span class="p">(</span><span class="nv">$request</span><span class="p">);</span>
</pre>
<h2 class="Heading"><a class="Anchor" aria-hidden="true" id="webhook.timeout" href="#webhook.timeout">
@@ -1520,16 +1539,16 @@ $resp = $client-&gt;post($request);
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>
</a>PHP</h3>
<pre class="chroma">use TheCodingMachine\Gotenberg\Client;
use TheCodingMachine\Gotenberg\DocumentFactory;
use TheCodingMachine\Gotenberg\HTMLRequest;
<pre class="chroma"><span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\Client</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\DocumentFactory</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\HTMLRequest</span><span class="p">;</span>
$client = new Client(&#39;http://localhost:3000&#39;, new \Http\Adapter\Guzzle6\Client());
$index = DocumentFactory::makeFromPath(&#39;index.html&#39;, &#39;/path/to/file&#39;);
$request = new HTMLRequest($index);
$request-&gt;setWebhookURL(&#39;http://myapp.com/webhook/&#39;);
$request-&gt;setWebhookURLTimeout(2.5);
$resp = $client-&gt;post($request);
<span class="nv">$client</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Client</span><span class="p">(</span><span class="s1">&#39;http://localhost:3000&#39;</span><span class="p">,</span> <span class="k">new</span> <span class="nx">\Http\Adapter\Guzzle6\Client</span><span class="p">());</span>
<span class="nv">$index</span> <span class="o">=</span> <span class="nx">DocumentFactory</span><span class="o">::</span><span class="na">makeFromPath</span><span class="p">(</span><span class="s1">&#39;index.html&#39;</span><span class="p">,</span> <span class="s1">&#39;/path/to/file&#39;</span><span class="p">);</span>
<span class="nv">$request</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">HTMLRequest</span><span class="p">(</span><span class="nv">$index</span><span class="p">);</span>
<span class="nv">$request</span><span class="o">-&gt;</span><span class="na">setWebhookURL</span><span class="p">(</span><span class="s1">&#39;http://myapp.com/webhook/&#39;</span><span class="p">);</span>
<span class="nv">$request</span><span class="o">-&gt;</span><span class="na">setWebhookURLTimeout</span><span class="p">(</span><span class="mf">2.5</span><span class="p">);</span>
<span class="nv">$resp</span> <span class="o">=</span> <span class="nv">$client</span><span class="o">-&gt;</span><span class="na">post</span><span class="p">(</span><span class="nv">$request</span><span class="p">);</span>
</pre>
<h2 class="Heading"><a class="Anchor" aria-hidden="true" id="webhook.custom_http_headers" href="#webhook.custom_http_headers">
@@ -1579,16 +1598,16 @@ canonical key for <code>accept-encoding</code> is <code>Accept-Encoding</code>.<
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>
</a>PHP</h3>
<pre class="chroma">use TheCodingMachine\Gotenberg\Client;
use TheCodingMachine\Gotenberg\DocumentFactory;
use TheCodingMachine\Gotenberg\HTMLRequest;
<pre class="chroma"><span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\Client</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\DocumentFactory</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\HTMLRequest</span><span class="p">;</span>
$client = new Client(&#39;http://localhost:3000&#39;, new \Http\Adapter\Guzzle6\Client());
$index = DocumentFactory::makeFromPath(&#39;index.html&#39;, &#39;/path/to/file&#39;);
$request = new HTMLRequest($index);
$request-&gt;setWebhookURL(&#39;http://myapp.com/webhook/&#39;);
$request-&gt;addWebhookURLHTTPHeader(&#39;Your-Header&#39;, &#39;Foo&#39;);
$resp = $client-&gt;post($request);
<span class="nv">$client</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Client</span><span class="p">(</span><span class="s1">&#39;http://localhost:3000&#39;</span><span class="p">,</span> <span class="k">new</span> <span class="nx">\Http\Adapter\Guzzle6\Client</span><span class="p">());</span>
<span class="nv">$index</span> <span class="o">=</span> <span class="nx">DocumentFactory</span><span class="o">::</span><span class="na">makeFromPath</span><span class="p">(</span><span class="s1">&#39;index.html&#39;</span><span class="p">,</span> <span class="s1">&#39;/path/to/file&#39;</span><span class="p">);</span>
<span class="nv">$request</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">HTMLRequest</span><span class="p">(</span><span class="nv">$index</span><span class="p">);</span>
<span class="nv">$request</span><span class="o">-&gt;</span><span class="na">setWebhookURL</span><span class="p">(</span><span class="s1">&#39;http://myapp.com/webhook/&#39;</span><span class="p">);</span>
<span class="nv">$request</span><span class="o">-&gt;</span><span class="na">addWebhookURLHTTPHeader</span><span class="p">(</span><span class="s1">&#39;Your-Header&#39;</span><span class="p">,</span> <span class="s1">&#39;Foo&#39;</span><span class="p">);</span>
<span class="nv">$resp</span> <span class="o">=</span> <span class="nv">$client</span><span class="o">-&gt;</span><span class="na">post</span><span class="p">(</span><span class="nv">$request</span><span class="p">);</span>
</pre>
</div>
@@ -1638,16 +1657,16 @@ Otherwise a random filename is used.</p>
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>
</a>PHP</h3>
<pre class="chroma">use TheCodingMachine\Gotenberg\Client;
use TheCodingMachine\Gotenberg\DocumentFactory;
use TheCodingMachine\Gotenberg\HTMLRequest;
use TheCodingMachine\Gotenberg\Request;
<pre class="chroma"><span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\Client</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\DocumentFactory</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\HTMLRequest</span><span class="p">;</span>
<span class="k">use</span> <span class="nx">TheCodingMachine\Gotenberg\Request</span><span class="p">;</span>
$client = new Client(&#39;http://localhost:3000&#39;, new \Http\Adapter\Guzzle6\Client());
$index = DocumentFactory::makeFromPath(&#39;index.html&#39;, &#39;/path/to/file&#39;);
$request = new HTMLRequest($index);
$request-&gt;setResultFilename(&#39;foo.pdf&#39;);
$resp = $client-&gt;post($request);
<span class="nv">$client</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Client</span><span class="p">(</span><span class="s1">&#39;http://localhost:3000&#39;</span><span class="p">,</span> <span class="k">new</span> <span class="nx">\Http\Adapter\Guzzle6\Client</span><span class="p">());</span>
<span class="nv">$index</span> <span class="o">=</span> <span class="nx">DocumentFactory</span><span class="o">::</span><span class="na">makeFromPath</span><span class="p">(</span><span class="s1">&#39;index.html&#39;</span><span class="p">,</span> <span class="s1">&#39;/path/to/file&#39;</span><span class="p">);</span>
<span class="nv">$request</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">HTMLRequest</span><span class="p">(</span><span class="nv">$index</span><span class="p">);</span>
<span class="nv">$request</span><span class="o">-&gt;</span><span class="na">setResultFilename</span><span class="p">(</span><span class="s1">&#39;foo.pdf&#39;</span><span class="p">);</span>
<span class="nv">$resp</span> <span class="o">=</span> <span class="nv">$client</span><span class="o">-&gt;</span><span class="na">post</span><span class="p">(</span><span class="nv">$request</span><span class="p">);</span>
</pre>
</div>
@@ -1696,14 +1715,14 @@ if the API is under heavy load.</p>
<p>For instance, using the following Docker Compose file:</p>
<pre class="chroma"><span class="k">version</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;3&#39;</span><span class="w">
<pre class="chroma"><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;3&#39;</span><span class="w">
</span><span class="w">
</span><span class="w"></span><span class="k">services</span><span class="p">:</span><span class="w">
</span><span class="w"></span><span class="nt">services</span><span class="p">:</span><span class="w">
</span><span class="w">
</span><span class="w"> </span><span class="c"># your others services</span><span class="w">
</span><span class="w"> </span><span class="c"># your other services</span><span class="w">
</span><span class="w">
</span><span class="w"> </span><span class="k">gotenberg</span><span class="p">:</span><span class="w">
</span><span class="w"> </span><span class="k">image</span><span class="p">:</span><span class="w"> </span>thecodingmachine/gotenberg<span class="p">:</span><span class="m">6</span><span class="w">
</span><span class="w"> </span><span class="nt">gotenberg</span><span class="p">:</span><span class="w">
</span><span class="w"> </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">thecodingmachine/gotenberg:6</span><span class="w">
</span></pre>
<p>You may now launch your services using:</p>
@@ -1761,7 +1780,8 @@ restart your Gotenberg instances from time to time to ensure a nominal behaviour
<ul>
<li>Follow the progress on the <a href="https://github.com/thecodingmachine/gotenberg">GitHub repository</a></li>
<li>Follow <a href="https://twitter.com/gulnap">@gulnap</a> on Twitter</li>
<li>Thanks to <a href="https://github.com/mafredri">@mafredri</a> for its help and its wonderful <a href="https://github.com/mafredri/cdp">cdp</a> library</li>
<li>Thanks to <a href="https://github.com/mafredri">@mafredri</a> for his help and his wonderful <a href="https://github.com/mafredri/cdp">cdp</a> library</li>
<li>OpenAPI documentation for the API can be found <a href="https://github.com/thecodingmachine/gotenberg/blob/master/docs/openapi.yaml">on GitHub</a></li>
</ul>
</div>

558
docs/openapi.yaml Normal file
View File

@@ -0,0 +1,558 @@
openapi: 3.0.0
info:
title: Gotenberg
version: 6.3.1
license:
name: MIT
url: 'https://github.com/thecodingmachine/gotenberg/blob/master/LICENSE'
contact:
url: 'https://github.com/thecodingmachine/gotenberg'
description: >-
A Docker-powered stateless API for converting HTML, Markdown and Office
documents to PDF.
servers:
- url: 'http://localhost:3000'
description: Local server with the default Docker image and port
paths:
/convert/html:
post:
summary: Convert a given HTML file to PDF
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.
An example `index.html` file can be as follows:
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My PDF</title>
</head>
<body>
<h1>Hello world!</h1>
</body>
</html>
```
You may also add a header and/or a footer in the resulting PDF by
sending a file named `header.html` and `footer.html` respectively. Both
the header and the footer files has to be a complete HTML document. An
example `footer.html` can be as follows:
```html
<html>
<head>
<style>
body {
font-size: 8rem;
margin: 4rem auto;
}
</style>
</head>
<body>
<p>
<span class="pageNumber"></span> of <span class="totalPages"></span>
</p>
</body>
</html>
```
The following classes will allow you to inject printing values in your
document:
- `date`: Formatted print date.
- `title`: Document title.
- `pageNumber`: Current page number.
- `totalPage`: Total pages in the document.
There are some limitations with header and footer files:
- JavaScript is not executed.
- External resources are not loaded.
- The CSS properties are independant of the ones used in the
`index.html` file.
- `footer.html` CSS properties override the ones from `header.html`.
- Only fonts installed in the Docker image are loaded (see the fonts
section)
- Images only work using a `base64` encoded source, e.g. `<img
src="data:image/png;base64, iVBORw0K... />`
- `background-color` and `color` CSS properties require an additional
`-webkit-print-color-adjust: exact` CSS property in order to work.
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'
/convert/url:
post:
summary: Convert the contents of a given URL to PDF
description: >-
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.
requestBody:
required: true
description: >-
The URL conversion request has to be a `multipart/form-data` request
that includes a `remoteURL`. 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'
/convert/markdown:
post:
summary: Convert a Markdown file to PDF
description: >-
Send an HTML file called `index.html` as a multipart form request and
embed a markdown file into the HTML file using the Golang template
function `toHTML`, the API will convert the markdown and embed it into
the HTML this way and render the resulting page. Markdown conversion
works almost exactly the same way as HTML conversion, therefore 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.
An example `index.html` file can be as follows:
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My PDF</title>
</head>
<body>
{{ toHTML .DirPath "file.md" }}
</body>
</html>
```
Whereas your `file.md` file in the same level would be like:
```md
# Title
Content
```
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.
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'
/convert/office:
post:
summary: Convert an Office document to PDF
description: >-
Send one or more Office documents and get the resulting PDF file by
merging all the files. The following file extensions are accepted:
- `.txt`
- `.rtf`
- `.fodt`
- `.doc`
- `.docx`
- `.odt`
- `.xls`
- `.xlsx`
- `.ods`
- `.ppt`
- `.pptx`
- `.odp`
All files will be merged into a single PDF.
> **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.
requestBody:
content:
multipart/form-data:
schema:
$ref: '#/components/schemas/OfficeConvertRequestBody'
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'
content:
application/json:
schema:
type: object
properties: {}
/convert/merge:
post:
summary: Merge multiple PDFs into a single PDF
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.
requestBody:
content:
multipart/form-data:
schema:
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
resultFilename:
type: string
example: output.pdf
description: >-
If provided, the API will return the resulting PDF file with
the given filename. Otherwise a random filename is used.
required:
- files
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'
/ping:
get:
summary: Your GET endpoint
tags: []
responses:
'200':
description: The API is working fine.
operationId: ''
description: >-
A simple endpoint to use as a healthcheck for the API availability with
a simple request.
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.
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.
resultFilename:
type: string
example: output.pdf
description: >-
If provided, the API will return the resulting PDF file with the
given filename. Otherwise a random filename is used.
required:
- files
MarkdownConvertRequestBody:
title: Markdown 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.
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.
resultFilename:
type: string
example: output.pdf
description: >-
If provided, the API will return the resulting PDF file with the
given filename. Otherwise a random filename is used.
required:
- files
URLConvertRequestBody:
title: URL Conversion Request Body
type: object
properties:
remoteURL:
type: string
example: 'https://google.com'
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.
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.
resultFilename:
type: string
example: output.pdf
description: >-
If provided, the API will return the resulting PDF file with the
given filename. Otherwise a random filename is used.
required:
- remoteURL
OfficeConvertRequestBody:
title: Office 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
pageRanges:
type: string
example: 1-3
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**.
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.
resultFilename:
type: string
example: output.pdf
description: >-
If provided, the API will return the resulting PDF file with the
given filename. Otherwise a random filename is used.
required:
- files
MergeFilesRequestBody:
title: Merge Files 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
resultFilename:
type: string
example: output.pdf
description: >-
If provided, the API will return the resulting PDF file with the
given filename. Otherwise a random filename is used.
required:
- files
securitySchemes: {}
responses:
SuccessfulPDF:
description: Resulting PDF file from the conversion.
content:
application/pdf:
schema:
type: string
format: binary

32
go.mod
View File

@@ -1,30 +1,26 @@
module github.com/thecodingmachine/gotenberg
go 1.13
go 1.14
require (
github.com/dustin/go-humanize v1.0.0
github.com/google/go-cmp v0.3.1 // indirect
github.com/gorilla/websocket v1.4.1 // indirect
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
github.com/kr/pretty v0.1.0 // indirect
github.com/labstack/echo/v4 v4.1.11
github.com/kr/text v0.2.0 // indirect
github.com/labstack/echo/v4 v4.1.16
github.com/labstack/gommon v0.3.0
github.com/mafredri/cdp v0.25.0
github.com/mattn/go-colorable v0.1.4 // indirect
github.com/mattn/go-isatty v0.0.11
github.com/mafredri/cdp v0.28.0
github.com/mattn/go-isatty v0.0.12
github.com/microcosm-cc/bluemonday v1.0.2
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2
github.com/russross/blackfriday/v2 v2.0.1
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
github.com/sirupsen/logrus v1.4.2
github.com/stretchr/testify v1.4.0
github.com/valyala/fasttemplate v1.1.0 // indirect
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 // indirect
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 // indirect
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 // indirect
github.com/sirupsen/logrus v1.6.0
github.com/stretchr/testify v1.6.1
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 // indirect
golang.org/x/net v0.0.0-20200602114024-627f9648deb9 // indirect
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 // indirect
golang.org/x/text v0.3.2
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
gopkg.in/yaml.v2 v2.2.7 // indirect
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c // indirect
)

100
go.sum
View File

@@ -1,41 +1,39 @@
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/labstack/echo/v4 v4.1.11 h1:z0BZoArY4FqdpUEl+wlHp4hnr/oSR6MTmQmv8OHSoww=
github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/labstack/echo/v4 v4.1.16 h1:8swiwjE5Jkai3RPfZoahp8kjVCRNq+y7Q0hPji2Kz0o=
github.com/labstack/echo/v4 v4.1.16/go.mod h1:awO+5TzAjvL8XpibdsfXxPgHr+orhtXZJZIQCVjogKI=
github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0=
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
github.com/mafredri/cdp v0.25.0 h1:nXhdpjP8Nrhw+9jnMlljEF6xs2B3vXI/wSiKDOvOXGs=
github.com/mafredri/cdp v0.25.0/go.mod h1:hgdiA0yp1uqhSaDOHJWPgXpMbh+LAfUdD9vbN2AM8gE=
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
github.com/mafredri/cdp v0.28.0 h1:v/LWC3GpactA1EaS737bl+eNUhvOaDh+FzvyoDxNM5o=
github.com/mafredri/cdp v0.28.0/go.mod h1:11586MgpyJuQR4qiXQ/7HBame2FtGIBzymaUHZcQ0PY=
github.com/mafredri/go-lint v0.0.0-20180911205320-920981dfc79e/go.mod h1:k/zdyxI3q6dup24o8xpYjJKTCf2F7rfxLp6w/efTiWs=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/microcosm-cc/bluemonday v1.0.2 h1:5lPfLTTAvAbtS0VqT+94yOtFnGfUWYyx0+iToC3Os3s=
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc=
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -44,47 +42,61 @@ github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/valyala/fasttemplate v1.1.0 h1:RZqt0yGBsps8NGvLSGW804QQqCUYYLsaOjTVHy1Ocw4=
github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g=
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM=
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM=
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 h1:gSbV7h1NRL2G1xTg/owz62CST1oJBmxy4QpMMregXVQ=
golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 h1:OjiUf46hAmXblsZdnoSXsEUSKU8r1UEzcL5RVZ4gO9Y=
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200330183114-f8bfb4ee3038 h1:FUufmZ17Dp41BwLDPii6R8vp0QtIquTiDIxcbFlY1ek=
golang.org/x/tools v0.0.0-20200330183114-f8bfb4ee3038/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c h1:grhR+C34yXImVGp7EzNk+DTIk+323eIUWOmEevy6bDo=
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -8,6 +8,7 @@ import (
"github.com/labstack/echo/v4"
"github.com/thecodingmachine/gotenberg/internal/app/xhttp/pkg/context"
"github.com/thecodingmachine/gotenberg/internal/app/xhttp/pkg/resource"
"github.com/thecodingmachine/gotenberg/internal/pkg/chrome"
"github.com/thecodingmachine/gotenberg/internal/pkg/conf"
"github.com/thecodingmachine/gotenberg/internal/pkg/printer"
"github.com/thecodingmachine/gotenberg/internal/pkg/xerror"
@@ -69,7 +70,17 @@ func pingHandler(c echo.Context) error {
const op string = "xhttp.pingHandler"
ctx := context.MustCastFromEchoContext(c)
logger := ctx.XLogger()
config := ctx.Config()
logger.DebugOp(op, "handling ping request...")
if !config.DisableGoogleChrome() {
_, err := chrome.IsViable(logger)
if err != nil {
return xerror.New(op, err)
}
}
return nil
}
@@ -239,13 +250,13 @@ func convert(ctx context.Context, p printer.Printer) error {
// and directly return the resulting PDF file
// or an error.
if !r.HasArg(resource.WebhookURLArgKey) {
logger.DebugfOp(op, "no '%s' found, converting synchronously", resource.WebhookURLArgKey)
logger.DebugOpf(op, "no '%s' found, converting synchronously", resource.WebhookURLArgKey)
return convertSync(ctx, p, filename, fpath)
}
// as a webhook URL has been given, we
// run the following lines in a goroutine so that
// it doesn't block.
logger.DebugfOp(op, "'%s' found, converting asynchronously", resource.WebhookURLArgKey)
logger.DebugOpf(op, "'%s' found, converting asynchronously", resource.WebhookURLArgKey)
return convertAsync(ctx, p, filename, fpath)
}
if err := resolver(); err != nil {
@@ -264,7 +275,7 @@ func convertSync(ctx context.Context, p printer.Printer, filename, fpath string)
return err
}
if !r.HasArg(resource.ResultFilenameArgKey) {
logger.DebugfOp(
logger.DebugOpf(
op,
"no '%s' found, using generated filename '%s'",
resource.RemoteURLArgKey,
@@ -275,7 +286,7 @@ func convertSync(ctx context.Context, p printer.Printer, filename, fpath string)
}
return nil
}
logger.DebugfOp(
logger.DebugOpf(
op,
"'%s' found, so not using generated filename",
resource.ResultFilenameArgKey,
@@ -308,7 +319,7 @@ func convertAsync(ctx context.Context, p printer.Printer, filename, fpath string
return xerror.New(op, err)
}
go func() {
defer r.Close() // nolint: errcheck
defer r.Close()
if err := p.Print(fpath); err != nil {
xerr := xerror.New(op, err)
logger.ErrorOp(xerror.Op(xerr), xerr)
@@ -320,8 +331,8 @@ func convertAsync(ctx context.Context, p printer.Printer, filename, fpath string
logger.ErrorOp(xerror.Op(xerr), xerr)
return
}
defer f.Close() // nolint: errcheck
logger.DebugfOp(
defer f.Close()
logger.DebugOpf(
op,
"preparing to send result file '%s' to '%s'...",
filename,
@@ -342,13 +353,13 @@ func convertAsync(ctx context.Context, p printer.Printer, filename, fpath string
if len(customHTTPHeaders) > 0 {
for key, value := range customHTTPHeaders {
req.Header.Set(key, value)
logger.DebugfOp(op, "set '%s' to custom HTTP header '%s'", value, key)
logger.DebugOpf(op, "set '%s' to custom HTTP header '%s'", value, key)
}
} else {
logger.DebugOp(op, "skipping custom HTTP headers as none have been provided...")
}
// send the result file.
logger.DebugfOp(
logger.DebugOpf(
op,
"sending result file '%s' to '%s'...",
filename,
@@ -360,8 +371,8 @@ func convertAsync(ctx context.Context, p printer.Printer, filename, fpath string
logger.ErrorOp(xerror.Op(xerr), xerr)
return
}
defer resp.Body.Close() // nolint: errcheck
logger.DebugfOp(
defer resp.Body.Close()
logger.DebugOpf(
op,
"result file '%s' sent to '%s'",
filename,

View File

@@ -56,6 +56,10 @@ func chromePrinterOptions(r resource.Resource, config conf.Config) (printer.Chro
if err != nil {
return printer.ChromePrinterOptions{}, err
}
scale, err := resource.ScaleArg(r, config)
if err != nil {
return printer.ChromePrinterOptions{}, err
}
return printer.ChromePrinterOptions{
WaitTimeout: waitTimeout,
WaitDelay: waitDelay,
@@ -71,6 +75,7 @@ func chromePrinterOptions(r resource.Resource, config conf.Config) (printer.Chro
PageRanges: pageRanges,
RpccBufferSize: googleChromeRpccBufferSize,
CustomHTTPHeaders: make(map[string]string),
Scale: scale,
}, nil
}
opts, err := resolver()

View File

@@ -4,6 +4,7 @@ import (
"fmt"
"io"
"net/http"
"path/filepath"
"reflect"
"strconv"
"strings"
@@ -105,8 +106,10 @@ func (ctx *Context) WithResource(directoryName string) error {
if err != nil {
return r, err
}
defer in.Close() // nolint: errcheck
if err := r.WithFile(fh.Filename, in); err != nil {
defer in.Close()
// avoid directory traversal.
filename := filepath.Base(fh.Filename)
if err := r.WithFile(filename, in); err != nil {
return r, err
}
}
@@ -167,14 +170,14 @@ func (ctx Context) LogRequestResult(err error, isDebug bool) error {
"bytes_out": bytesOut(resp),
}
if err != nil {
ctx.logger.WithFields(fields).ErrorfOp(op, "request failed")
ctx.logger.WithFields(fields).ErrorOpf(op, "request failed")
return err
}
if isDebug {
ctx.logger.WithFields(fields).DebugfOp(op, "request handled")
ctx.logger.WithFields(fields).DebugOpf(op, "request handled")
return nil
}
ctx.logger.WithFields(fields).InfofOp(op, "request handled")
ctx.logger.WithFields(fields).InfoOpf(op, "request handled")
return nil
}

View File

@@ -57,6 +57,9 @@ const (
// GoogleChromeRpccBufferSizeArgKey is the key
// of the argument "googleChromeRpccBufferSize".
GoogleChromeRpccBufferSizeArgKey ArgKey = "googleChromeRpccBufferSize"
// ScaleArgKey is the key
// of the argument "scale".
ScaleArgKey ArgKey = "scale"
)
/*
@@ -81,6 +84,7 @@ func ArgKeys() []ArgKey {
LandscapeArgKey,
PageRangesArgKey,
GoogleChromeRpccBufferSizeArgKey,
ScaleArgKey,
}
}
@@ -294,3 +298,22 @@ func GoogleChromeRpccBufferSizeArg(r Resource, config conf.Config) (int64, error
}
return result, nil
}
/*
ScaleArg is a helper for retrieving
the "scale" argument as float64.
*/
func ScaleArg(r Resource, config conf.Config) (float64, error) {
const op string = "resource.ScaleArg"
opts := printer.DefaultChromePrinterOptions(config)
result, err := r.Float64Arg(
ScaleArgKey,
opts.Scale,
xassert.Float64NotInferiorTo(0.1),
xassert.Float64NotSuperiorTo(2.0),
)
if err != nil {
return result, xerror.New(op, err)
}
return result, nil
}

View File

@@ -26,6 +26,7 @@ func TestArgKeys(t *testing.T) {
LandscapeArgKey,
PageRangesArgKey,
GoogleChromeRpccBufferSizeArgKey,
ScaleArgKey,
}
assert.Equal(t, expected, ArgKeys())
}
@@ -346,3 +347,50 @@ func TestGoogleChromeRpccBufferSizeArg(t *testing.T) {
err = r.Close()
assert.Nil(t, err)
}
func TestScaleArg(t *testing.T) {
const (
resourceDirectoryName string = "foo"
defaultValue float64 = 1.0
)
var expected float64
logger := test.DebugLogger()
config := conf.DefaultConfig()
r, err := New(logger, resourceDirectoryName)
assert.Nil(t, err)
// argument does not exist.
expected = defaultValue
v, err := ScaleArg(r, config)
assert.Nil(t, err)
assert.Equal(t, expected, v)
// argument exist.
expected = 0.5
r.WithArg(ScaleArgKey, "0.5")
v, err = ScaleArg(r, config)
assert.Nil(t, err)
assert.Equal(t, expected, v)
// should not be OK as argument
// value is < 0.1.
expected = defaultValue
r.WithArg(ScaleArgKey, "-1.0")
v, err = ScaleArg(r, config)
test.AssertError(t, err)
assert.Equal(t, expected, v)
// should not be OK as argument
// value is > 2.0.
expected = defaultValue
r.WithArg(ScaleArgKey, "2.1")
v, err = ScaleArg(r, config)
test.AssertError(t, err)
assert.Equal(t, expected, v)
// should not be OK as
// argument value is invalid.
expected = defaultValue
r.WithArg(ScaleArgKey, "foo")
v, err = ScaleArg(r, config)
test.AssertError(t, err)
assert.Equal(t, expected, v)
// finally...
err = r.Close()
assert.Nil(t, err)
}

View File

@@ -24,8 +24,8 @@ func (f file) write(in io.Reader) error {
if err != nil {
return err
}
defer out.Close() // nolint: errcheck
if err := out.Chmod(0644); err != nil {
defer out.Close()
if err := out.Chmod(0600); err != nil {
return err
}
if _, err := io.Copy(out, in); err != nil {

View File

@@ -50,7 +50,7 @@ func New(logger xlog.Logger, directoryName string) (Resource, error) {
if err != nil {
return Resource{}, xerror.New(op, err)
}
logger.DebugfOp(op, "resource directory '%s' created", directoryName)
logger.DebugOpf(op, "resource directory '%s' created", directoryName)
return Resource{
logger: logger,
dirPath: dirPath,
@@ -65,13 +65,13 @@ func New(logger xlog.Logger, directoryName string) (Resource, error) {
func (r Resource) Close() error {
const op string = "resource.Resource.Close"
if _, err := os.Stat(r.dirPath); os.IsNotExist(err) {
r.logger.DebugfOp(op, "resource directory '%s' does not exist, nothing to remove", r.dirPath)
r.logger.DebugOpf(op, "resource directory '%s' does not exist, nothing to remove", r.dirPath)
return nil
}
if err := os.RemoveAll(r.dirPath); err != nil {
return xerror.New(op, err)
}
r.logger.DebugfOp(op, "resource directory '%s' removed", r.dirPath)
r.logger.DebugOpf(op, "resource directory '%s' removed", r.dirPath)
return nil
}
@@ -84,17 +84,17 @@ func (r *Resource) WithCustomHTTPHeader(key string, value string) {
if strings.Contains(canonicalKey, RemoteURLCustomHTTPHeaderCanonicalBaseKey) ||
strings.Contains(canonicalKey, WebhookURLCustomHTTPHeaderCanonicalBaseKey) {
r.customHeaders[canonicalKey] = value
r.logger.DebugfOp(op, "added '%s' with value '%s' to resource custom HTTP headers", canonicalKey, value)
r.logger.DebugOpf(op, "added '%s' with value '%s' to resource custom HTTP headers", canonicalKey, value)
return
}
r.logger.DebugfOp(op, "skipping '%s' as it is not a custom HTTP header...", canonicalKey)
r.logger.DebugOpf(op, "skipping '%s' as it is not a custom HTTP header...", canonicalKey)
}
// WithArg add a new argument to the Resource.
func (r *Resource) WithArg(key ArgKey, value string) {
const op string = "resource.Resource.WithArg"
r.args[key] = value
r.logger.DebugfOp(op, "added '%s' with value '%s' to resource args", key, value)
r.logger.DebugOpf(op, "added '%s' with value '%s' to resource args", key, value)
}
// WithFile add a new file to the Resource.
@@ -112,7 +112,7 @@ func (r *Resource) WithFile(filename string, in io.Reader) error {
return err
}
r.files[filename] = file
r.logger.DebugfOp(op, "resource file '%s' created", filename)
r.logger.DebugOpf(op, "resource file '%s' created", filename)
return nil
}
if err := resolver(); err != nil {

View File

@@ -16,11 +16,11 @@ import (
)
// Start starts Google Chrome headless in background.
func Start(logger xlog.Logger) error {
func Start(logger xlog.Logger, ignoreCertificateErrors bool) error {
const op string = "chrome.Start"
logger.DebugOp(op, "starting new Google Chrome headless process on port 9222...")
resolver := func() error {
cmd, err := cmd(logger)
cmd, err := cmd(logger, ignoreCertificateErrors)
if err != nil {
return err
}
@@ -31,8 +31,9 @@ func Start(logger xlog.Logger) error {
}
// if the process failed to start correctly,
// we have to restart it.
if !isViable(logger) {
return restart(logger, cmd.Process)
isViable, _ := IsViable(logger)
if !isViable {
return restart(logger, cmd.Process, ignoreCertificateErrors)
}
return nil
}
@@ -42,7 +43,7 @@ func Start(logger xlog.Logger) error {
return nil
}
func cmd(logger xlog.Logger) (*exec.Cmd, error) {
func cmd(logger xlog.Logger, ignoreCertificateErrors bool) (*exec.Cmd, error) {
const op string = "chrome.cmd"
binary := "google-chrome-stable"
args := []string{
@@ -66,6 +67,11 @@ func cmd(logger xlog.Logger) (*exec.Cmd, error) {
"--mute-audio",
"--no-first-run",
}
if ignoreCertificateErrors {
args = append(args, "--ignore-certificate-errors")
}
cmd, err := xexec.Command(logger, binary, args...)
if err != nil {
return nil, xerror.New(op, err)
@@ -93,7 +99,7 @@ func kill(logger xlog.Logger, proc *os.Process) error {
return nil
}
func restart(logger xlog.Logger, proc *os.Process) error {
func restart(logger xlog.Logger, proc *os.Process, ignoreCertificateErrors bool) error {
const op string = "chrome.restart"
logger.DebugOp(op, "restarting Google Chrome headless process using port 9222...")
resolver := func() error {
@@ -101,7 +107,7 @@ func restart(logger xlog.Logger, proc *os.Process) error {
if err := kill(logger, proc); err != nil {
return err
}
cmd, err := cmd(logger)
cmd, err := cmd(logger, ignoreCertificateErrors)
if err != nil {
return err
}
@@ -112,8 +118,9 @@ func restart(logger xlog.Logger, proc *os.Process) error {
}
// if the process failed to restart correctly,
// we have to restart it again.
if !isViable(logger) {
return restart(logger, cmd.Process)
isViable, _ := IsViable(logger)
if !isViable {
return restart(logger, cmd.Process, ignoreCertificateErrors)
}
return nil
}
@@ -123,48 +130,54 @@ func restart(logger xlog.Logger, proc *os.Process) error {
return nil
}
func isViable(logger xlog.Logger) bool {
// IsViable checks if Google Chrome is healthy.
func IsViable(logger xlog.Logger) (bool, error) {
const (
op string = "chrome.isViable"
op string = "chrome.IsViable"
maxViabilityTests int = 20
)
viable := func() bool {
viable := func() (bool, error) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
endpoint := "http://localhost:9222"
logger.DebugfOp(
logger.DebugOpf(
op,
"checking Google Chrome headless process viability via endpoint '%s/json/version'",
endpoint,
)
v, err := devtool.New(endpoint).Version(ctx)
if err != nil {
logger.DebugfOp(
logger.DebugOpf(
op,
"Google Chrome headless is not viable as endpoint returned '%v'",
err.Error(),
)
return false
return false, err
}
logger.DebugfOp(
logger.DebugOpf(
op,
"Google Chrome headless is viable as endpoint returned '%v'",
v,
)
return true
return true, nil
}
result := false
var err error
for i := 0; i < maxViabilityTests && !result; i++ {
warmup(logger)
result = viable()
result, err = viable()
}
return result
return result, err
}
func warmup(logger xlog.Logger) {
const op string = "chrome.warmup"
warmupTime := xtime.Duration(0.5)
logger.DebugfOp(
const (
op string = "chrome.warmup"
seconds float64 = 0.5
)
warmupTime := xtime.Duration(seconds)
logger.DebugOpf(
op,
"waiting '%v' for allowing Google Chrome to warmup",
warmupTime,

View File

@@ -40,41 +40,46 @@ const (
// DefaultGoogleChromeRpccBufferSizeEnvVar contains the name
// of the environment variable "DEFAULT_GOOGLE_CHROME_RPCC_BUFFER_SIZE".
DefaultGoogleChromeRpccBufferSizeEnvVar string = "DEFAULT_GOOGLE_CHROME_RPCC_BUFFER_SIZE"
// GoogleChromeIgnoreCertificateErrorsEnvVar contains the name
// of the environment variable "GOOGLE_CHROME_IGNORE_CERTIFICATE_ERRORS".
GoogleChromeIgnoreCertificateErrorsEnvVar string = "GOOGLE_CHROME_IGNORE_CERTIFICATE_ERRORS"
)
// Config contains the application
// configuration.
type Config struct {
maximumWaitTimeout float64
maximumWaitDelay float64
maximumWebhookURLTimeout float64
defaultWaitTimeout float64
defaultWebhookURLTimeout float64
defaultListenPort int64
disableGoogleChrome bool
disableUnoconv bool
logLevel xlog.Level
rootPath string
maximumGoogleChromeRpccBufferSize int64
defaultGoogleChromeRpccBufferSize int64
maximumWaitTimeout float64
maximumWaitDelay float64
maximumWebhookURLTimeout float64
defaultWaitTimeout float64
defaultWebhookURLTimeout float64
defaultListenPort int64
disableGoogleChrome bool
disableUnoconv bool
googleChromeIgnoreCertificateErrors bool
logLevel xlog.Level
rootPath string
maximumGoogleChromeRpccBufferSize int64
defaultGoogleChromeRpccBufferSize int64
}
// DefaultConfig returns the default
// configuration.
func DefaultConfig() Config {
return Config{
maximumWaitTimeout: 30.0,
maximumWaitDelay: 10.0,
maximumWebhookURLTimeout: 30.0,
defaultWaitTimeout: 10.0,
defaultWebhookURLTimeout: 10.0,
defaultListenPort: 3000,
disableGoogleChrome: false,
disableUnoconv: false,
logLevel: xlog.InfoLevel,
rootPath: "/",
maximumGoogleChromeRpccBufferSize: 104857600, // ~100 MB
defaultGoogleChromeRpccBufferSize: 1048576, // 1 MB
maximumWaitTimeout: 30.0,
maximumWaitDelay: 10.0,
maximumWebhookURLTimeout: 30.0,
defaultWaitTimeout: 10.0,
defaultWebhookURLTimeout: 10.0,
defaultListenPort: 3000,
disableGoogleChrome: false,
disableUnoconv: false,
logLevel: xlog.InfoLevel,
rootPath: "/",
maximumGoogleChromeRpccBufferSize: 104857600, // ~100 MB
defaultGoogleChromeRpccBufferSize: 1048576, // 1 MB
googleChromeIgnoreCertificateErrors: false,
}
}
@@ -188,6 +193,14 @@ func FromEnv() (Config, error) {
if err != nil {
return c, err
}
googleChromeIgnoreCertificateErrors, err := xassert.BoolFromEnv(
GoogleChromeIgnoreCertificateErrorsEnvVar,
c.googleChromeIgnoreCertificateErrors,
)
c.googleChromeIgnoreCertificateErrors = googleChromeIgnoreCertificateErrors
if err != nil {
return c, err
}
return c, nil
}
result, err := resolver()
@@ -274,3 +287,7 @@ func (c Config) MaximumGoogleChromeRpccBufferSize() int64 {
func (c Config) DefaultGoogleChromeRpccBufferSize() int64 {
return c.defaultGoogleChromeRpccBufferSize
}
func (c Config) GoogleChromeIgnoreCertificateErrors() bool {
return c.googleChromeIgnoreCertificateErrors
}

View File

@@ -380,6 +380,43 @@ func TestDefaultGoogleChromeRpccBufferSizeFromEnv(t *testing.T) {
os.Unsetenv(DefaultGoogleChromeRpccBufferSizeEnvVar)
}
func TestGoogleChromeIgnoreCertificateErrorsFromEnv(t *testing.T) {
var (
expected Config
result Config
err error
)
// GOOGLE_CHROME_IGNORE_CERTIFICATE_ERRORS correctly set to true.
os.Setenv(GoogleChromeIgnoreCertificateErrorsEnvVar, "1")
expected = DefaultConfig()
expected.googleChromeIgnoreCertificateErrors = true
result, err = FromEnv()
assert.Nil(t, err)
assert.Equal(t, expected, result)
os.Unsetenv(GoogleChromeIgnoreCertificateErrorsEnvVar)
// GOOGLE_CHROME_IGNORE_CERTIFICATE_ERRORS correctly set to false.
os.Setenv(GoogleChromeIgnoreCertificateErrorsEnvVar, "0")
expected = DefaultConfig()
expected.googleChromeIgnoreCertificateErrors = false
result, err = FromEnv()
assert.Nil(t, err)
assert.Equal(t, expected, result)
os.Unsetenv(GoogleChromeIgnoreCertificateErrorsEnvVar)
// GOOGLE_CHROME_IGNORE_CERTIFICATE_ERRORS wrongly set.
os.Setenv(GoogleChromeIgnoreCertificateErrorsEnvVar, "foo")
expected = DefaultConfig()
result, err = FromEnv()
test.AssertError(t, err)
assert.Equal(t, expected, result)
os.Unsetenv(GoogleChromeIgnoreCertificateErrorsEnvVar)
// GOOGLE_CHROME_IGNORE_CERTIFICATE_ERRORS not set at all.
expected = DefaultConfig()
expected.googleChromeIgnoreCertificateErrors = false
result, err = FromEnv()
assert.Nil(t, err)
assert.Equal(t, expected, result)
}
func TestGetters(t *testing.T) {
result := DefaultConfig()
assert.Equal(t, result.maximumWaitTimeout, result.MaximumWaitTimeout())
@@ -394,4 +431,5 @@ func TestGetters(t *testing.T) {
assert.Equal(t, result.rootPath, result.RootPath())
assert.Equal(t, result.maximumGoogleChromeRpccBufferSize, result.MaximumGoogleChromeRpccBufferSize())
assert.Equal(t, result.defaultGoogleChromeRpccBufferSize, result.DefaultGoogleChromeRpccBufferSize())
assert.Equal(t, result.googleChromeIgnoreCertificateErrors, result.GoogleChromeIgnoreCertificateErrors())
}

View File

@@ -45,6 +45,7 @@ type ChromePrinterOptions struct {
PageRanges string
RpccBufferSize int64
CustomHTTPHeaders map[string]string
Scale float64
}
// DefaultChromePrinterOptions returns the default
@@ -66,6 +67,7 @@ func DefaultChromePrinterOptions(config conf.Config) ChromePrinterOptions {
PageRanges: "",
RpccBufferSize: config.DefaultGoogleChromeRpccBufferSize(),
CustomHTTPHeaders: make(map[string]string),
Scale: 1.0,
}
}
@@ -92,10 +94,11 @@ func (p chromePrinter) Print(destination string) error {
if err != nil {
return err
}
defer devtConn.Close() // nolint: errcheck
defer devtConn.Close()
// create a new CDP Client that uses conn.
devtClient := cdp.NewClient(devtConn)
newContextTarget, err := devtClient.Target.CreateBrowserContext(ctx)
createBrowserContextArgs := target.NewCreateBrowserContextArgs()
newContextTarget, err := devtClient.Target.CreateBrowserContext(ctx, createBrowserContextArgs)
if err != nil {
return err
}
@@ -133,7 +136,7 @@ func (p chromePrinter) Print(destination string) error {
if err != nil {
return err
}
defer newContextConn.Close() // nolint: errcheck
defer newContextConn.Close()
// create a new CDP Client that uses newContextConn.
targetClient := cdp.NewClient(newContextConn)
/*
@@ -160,7 +163,7 @@ func (p chromePrinter) Print(destination string) error {
// apply a wait delay (if any).
if p.opts.WaitDelay > 0.0 {
// wait for a given amount of time (useful for javascript delay).
p.logger.DebugfOp(op, "applying a wait delay of '%.2fs'...", p.opts.WaitDelay)
p.logger.DebugOpf(op, "applying a wait delay of '%.2fs'...", p.opts.WaitDelay)
time.Sleep(xtime.Duration(p.opts.WaitDelay))
} else {
p.logger.DebugOp(op, "no wait delay to apply, moving on...")
@@ -176,12 +179,13 @@ func (p chromePrinter) Print(destination string) error {
SetDisplayHeaderFooter(true).
SetHeaderTemplate(p.opts.HeaderHTML).
SetFooterTemplate(p.opts.FooterHTML).
SetPrintBackground(true)
SetPrintBackground(true).
SetScale(p.opts.Scale)
if p.opts.PageRanges != "" {
printToPdfArgs.SetPageRanges(p.opts.PageRanges)
}
// print the page to PDF.
print, err := targetClient.Page.PrintToPDF(
// printToPDF the page to PDF.
printToPDF, err := targetClient.Page.PrintToPDF(
ctx,
printToPdfArgs,
)
@@ -206,7 +210,7 @@ func (p chromePrinter) Print(destination string) error {
}
return err
}
if err := ioutil.WriteFile(destination, print.Data, 0644); err != nil {
if err := ioutil.WriteFile(destination, printToPDF.Data, 0600); err != nil {
return err
}
return nil
@@ -279,7 +283,7 @@ func (p chromePrinter) setCustomHTTPHeaders(ctx context.Context, client *cdp.Cli
// useless but for the logs.
for key, value := range p.opts.CustomHTTPHeaders {
customHTTPHeaders[key] = value
p.logger.DebugfOp(op, "set '%s' to custom HTTP header '%s'", value, key)
p.logger.DebugOpf(op, "set '%s' to custom HTTP header '%s'", value, key)
}
b, err := json.Marshal(customHTTPHeaders)
if err != nil {
@@ -310,22 +314,22 @@ func (p chromePrinter) listenEvents(ctx context.Context, client *cdp.Client) err
if err != nil {
return err
}
defer domContentEventFired.Close() // nolint: errcheck
defer domContentEventFired.Close()
loadEventFired, err := client.Page.LoadEventFired(ctx)
if err != nil {
return err
}
defer loadEventFired.Close() // nolint: errcheck
defer loadEventFired.Close()
lifecycleEvent, err := client.Page.LifecycleEvent(ctx)
if err != nil {
return err
}
defer lifecycleEvent.Close() // nolint: errcheck
defer lifecycleEvent.Close()
loadingFinished, err := client.Network.LoadingFinished(ctx)
if err != nil {
return err
}
defer loadingFinished.Close() // nolint: errcheck
defer loadingFinished.Close()
if _, err := client.Page.Navigate(ctx, page.NewNavigateArgs(p.url)); err != nil {
return err
}
@@ -354,7 +358,7 @@ func (p chromePrinter) listenEvents(ctx context.Context, client *cdp.Client) err
if err != nil {
return err
}
p.logger.DebugfOp(op, "event '%s' received", ev.Name)
p.logger.DebugOpf(op, "event '%s' received", ev.Name)
if ev.Name == networkIdleEventName {
break
}

View File

@@ -3,15 +3,14 @@ package printer
import (
"bytes"
"fmt"
"html/template"
"io/ioutil"
"path/filepath"
"github.com/microcosm-cc/bluemonday"
"github.com/russross/blackfriday/v2"
"github.com/thecodingmachine/gotenberg/internal/pkg/xerror"
"github.com/thecodingmachine/gotenberg/internal/pkg/xlog"
"github.com/thecodingmachine/gotenberg/internal/pkg/xrand"
"html/template"
"io/ioutil"
"path/filepath"
)
// NewMarkdownPrinter returns a Printer which
@@ -36,7 +35,7 @@ func NewMarkdownPrinter(logger xlog.Logger, fpath string, opts ChromePrinterOpti
baseFilename := xrand.Get()
dst := fmt.Sprintf("%s/%s.html", dirPath, baseFilename)
logger.DebugOp(op, "writing the HTML from previous conversion(s) into new file...")
if err := ioutil.WriteFile(dst, buffer.Bytes(), 0644); err != nil {
if err := ioutil.WriteFile(dst, buffer.Bytes(), 0600); err != nil {
return "", err
}
return fmt.Sprintf("file://%s", dst), nil
@@ -58,6 +57,8 @@ type templateData struct {
func markdownToHTML(dirPath, filename string) (template.HTML, error) {
const op string = "printer.markdownToHTML"
// avoid directory traversal.
filename = filepath.Base(filename)
fpath := fmt.Sprintf("%s/%s", dirPath, filename)
b, err := ioutil.ReadFile(fpath)
if err != nil {

View File

@@ -57,7 +57,7 @@ func (p mergePrinter) Print(destination string) error {
}
// see https://github.com/thecodingmachine/gotenberg/issues/139.
sort.Strings(p.fpaths)
p.logger.DebugfOp(op, "merging '%v'...", p.fpaths)
p.logger.DebugOpf(op, "merging '%v'...", p.fpaths)
resolver := func() error {
var args []string
args = append(args, p.fpaths...)

View File

@@ -64,11 +64,11 @@ func (p officePrinter) Print(destination string) error {
for i, fpath := range p.fpaths {
baseFilename := xrand.Get()
tmpDest := fmt.Sprintf("%s/%d%s.pdf", dirPath, i, baseFilename)
p.logger.DebugfOp(op, "converting '%s' to PDF...", fpath)
p.logger.DebugOpf(op, "converting '%s' to PDF...", fpath)
if err := p.unoconv(ctx, fpath, tmpDest); err != nil {
return err
}
p.logger.DebugfOp(op, "'%s.pdf' created", baseFilename)
p.logger.DebugOpf(op, "'%s.pdf' created", baseFilename)
fpaths[i] = tmpDest
}
if len(fpaths) == 1 {
@@ -94,13 +94,14 @@ func (p officePrinter) Print(destination string) error {
func (p officePrinter) unoconv(ctx context.Context, fpath, destination string) error {
const op string = "printer.unoconv"
resolver := func() error {
dirName := xrand.Get()
port, err := freeport.GetFreePort()
if err != nil {
return err
}
args := []string{
"--user-profile",
fmt.Sprintf("///tmp/%d", port),
fmt.Sprintf("///tmp/%s", dirName),
"--port",
fmt.Sprintf("%d", port),
"--format",
@@ -113,7 +114,11 @@ func (p officePrinter) unoconv(ctx context.Context, fpath, destination string) e
args = append(args, "--export", fmt.Sprintf("PageRange=%s", p.opts.PageRanges))
}
args = append(args, "--output", destination, fpath)
if err := xexec.Run(ctx, p.logger, "unoconv", args...); err != nil {
err = xexec.Run(ctx, p.logger, "unoconv", args...)
// always remove user profile folders created by LibreOffice.
// see https://github.com/thecodingmachine/gotenberg/issues/192.
go cleanupUserProfile(p.logger, dirName)
if err != nil {
// find a way to check it in the handlers?
if p.opts.PageRanges != "" && strings.Contains(err.Error(), "exit status 5") {
return xerror.Invalid(
@@ -132,6 +137,15 @@ func (p officePrinter) unoconv(ctx context.Context, fpath, destination string) e
return nil
}
func cleanupUserProfile(logger xlog.Logger, dirName string) {
const op = "printer.cleanupUserProfile"
path := fmt.Sprintf("/tmp/%s", dirName)
if err := os.RemoveAll(path); err != nil {
// find a way to bubble up this error?
logger.ErrorOpf(op, "failed to remove user profile directory '%s': %s", path, err.Error())
}
}
// Compile-time checks to ensure type implements desired interfaces.
var (
_ = Printer(new(officePrinter))

View File

@@ -12,5 +12,5 @@ type Printer interface {
func logOptions(logger xlog.Logger, opts interface{}) {
const op string = "printer.logOptions"
logger.DebugfOp(op, "options: %+v", opts)
logger.DebugOpf(op, "options: %+v", opts)
}

View File

@@ -14,7 +14,7 @@ import (
// times out after given seconds.
func WithTimeout(logger xlog.Logger, seconds float64) (context.Context, context.CancelFunc) {
const op string = "xcontext.WithTimeout"
logger.DebugfOp(op, "creating context with '%.2fs' of timeout...", seconds)
logger.DebugOpf(op, "creating context with '%.2fs' of timeout...", seconds)
return context.WithTimeout(context.Background(), xtime.Duration(seconds))
}

View File

@@ -84,11 +84,11 @@ func Run(ctx context.Context, logger xlog.Logger, binary string, args ...string)
}()
select {
case err := <-result:
logger.DebugfOp(op, "command '%s' finished", strings.Join(cmd.Args, " "))
logger.DebugOpf(op, "command '%s' finished", strings.Join(cmd.Args, " "))
kill()
return err
case <-ctx.Done():
logger.DebugfOp(op, "command '%s' failed to finish before context.Context deadline", strings.Join(cmd.Args, " "))
logger.DebugOpf(op, "command '%s' failed to finish before context.Context deadline", strings.Join(cmd.Args, " "))
kill()
return ctx.Err()
}
@@ -102,7 +102,7 @@ func Run(ctx context.Context, logger xlog.Logger, binary string, args ...string)
// LogBeforeExecute logs a command before its execution.
func LogBeforeExecute(logger xlog.Logger, cmd *exec.Cmd) {
const op string = "xexec.LogBeforeExecute"
logger.DebugfOp(op, "executing command: %s", strings.Join(cmd.Args, " "))
logger.DebugOpf(op, "executing command: %s", strings.Join(cmd.Args, " "))
}
func pipe(logger xlog.Logger, cmd *exec.Cmd) error {
@@ -139,7 +139,7 @@ func logCommandOutput(logger xlog.Logger, reader io.ReadCloser, outputType strin
}
op := buf.String()
r := bufio.NewReader(reader)
defer reader.Close() // nolint: errcheck
defer reader.Close()
for {
line, _, err := r.ReadLine()
if err != nil {

View File

@@ -104,9 +104,9 @@ func (l Logger) DebugOp(op, message string) {
l.entry.WithField("op", op).Debug(message)
}
// DebugfOp logs a debug message for given
// DebugOpf logs a debug message for given
// logical operation and format.
func (l Logger) DebugfOp(op, format string, args ...interface{}) {
func (l Logger) DebugOpf(op, format string, args ...interface{}) {
l.entry.WithField("op", op).Debugf(format, args...)
}
@@ -116,9 +116,9 @@ func (l Logger) InfoOp(op, message string) {
l.entry.WithField("op", op).Info(message)
}
// InfofOp logs an info message for given
// InfoOpf logs an info message for given
// logical operation and format.
func (l Logger) InfofOp(op, format string, args ...interface{}) {
func (l Logger) InfoOpf(op, format string, args ...interface{}) {
l.entry.WithField("op", op).Infof(format, args...)
}
@@ -128,9 +128,9 @@ func (l Logger) ErrorOp(op string, err error) {
l.entry.WithField("op", op).Error(err.Error())
}
// ErrorfOp logs an error message for given
// ErrorOpf logs an error message for given
// logical operation and format.
func (l Logger) ErrorfOp(op, format string, args ...interface{}) {
func (l Logger) ErrorOpf(op, format string, args ...interface{}) {
l.entry.WithField("op", op).Errorf(format, args...)
}

View File

@@ -21,7 +21,7 @@ if [ $VERSION_LENGTH -ne 3 ]; then
fi
docker build \
--build-arg VERSION=${VERSION} \
--build-arg VERSION=${VERSION} \
--build-arg TINI_VERSION=${TINI_VERSION} \
-t ${DOCKER_REGISTRY}/gotenberg:latest \
-t ${DOCKER_REGISTRY}/gotenberg:${SEMVER[0]} \

View File

@@ -14,7 +14,7 @@ func main() {
systemLogger.FatalOp(op, err)
}
// start Google Chrome headless.
if err := chrome.Start(systemLogger); err != nil {
if err := chrome.Start(systemLogger, config.GoogleChromeIgnoreCertificateErrors()); err != nil {
systemLogger.FatalOp(op, err)
}
}