mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-12 18:32:14 +01:00
Compare commits
57 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ff16cc54b | ||
|
|
3609cf54c0 | ||
|
|
198038f5ea | ||
|
|
b5a3df85a8 | ||
|
|
6e56c6e0d4 | ||
|
|
568f3d6007 | ||
|
|
6b505a1d06 | ||
|
|
acc7e91c55 | ||
|
|
8e43df8d95 | ||
|
|
ca81b382d3 | ||
|
|
a60d7976f5 | ||
|
|
46cf63da37 | ||
|
|
099079b962 | ||
|
|
5b8121516f | ||
|
|
23eacaa78a | ||
|
|
659292a3f1 | ||
|
|
f88756d3a8 | ||
|
|
81e7bab22a | ||
|
|
1eeb5fc2b0 | ||
|
|
126cdd73e4 | ||
|
|
2251267ae4 | ||
|
|
f3e4b6d9a2 | ||
|
|
b22379537b | ||
|
|
feb5bb0653 | ||
|
|
ebfe99be79 | ||
|
|
49c1b939d5 | ||
|
|
84a37f124b | ||
|
|
f0f48f4ddf | ||
|
|
cf15e3a9a1 | ||
|
|
927f98c66b | ||
|
|
782f6ac27e | ||
|
|
5140e4ec9a | ||
|
|
e255f9bfc5 | ||
|
|
765dc734a4 | ||
|
|
3abe8a1103 | ||
|
|
73333074fd | ||
|
|
a415786e1c | ||
|
|
a3a1077dea | ||
|
|
58a812c2c9 | ||
|
|
7e69ec4367 | ||
|
|
4623c01896 | ||
|
|
e85322de62 | ||
|
|
090ff3da13 | ||
|
|
fe068b1bfb | ||
|
|
d3b0a685b9 | ||
|
|
245c04fc5a | ||
|
|
94acb0c6ae | ||
|
|
ad4a16cc4e | ||
|
|
dae39113f6 | ||
|
|
1e1cb8fe57 | ||
|
|
606254d941 | ||
|
|
8fd51b9e9e | ||
|
|
3255f8af0c | ||
|
|
ca0784fa59 | ||
|
|
827060b6ce | ||
|
|
60c069cb1f | ||
|
|
70dfea5876 |
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -40,6 +40,6 @@ Fixes #
|
|||||||
- [ ] Have you lint your code locally prior to submission (`make lint`)?
|
- [ ] Have you lint your code locally prior to submission (`make lint`)?
|
||||||
- [ ] Have you written new tests for your core changes, as applicable?
|
- [ ] Have you written new tests for your core changes, as applicable?
|
||||||
- [ ] Have you successfully ran tests with your changes locally (`make tests`)?
|
- [ ] Have you successfully ran tests with your changes locally (`make tests`)?
|
||||||
- [ ] Have you updated the documentation (`make doc`)?
|
- [ ] Have you updated the documentation (Markdown files under `build > docs > content` and then `make doc`)?
|
||||||
- [ ] I have squashed any insignificant commits
|
- [ ] I have squashed any insignificant commits
|
||||||
- [ ] This change has comments for package types, values, functions, and non-obvious lines of code
|
- [ ] This change has comments for package types, values, functions, and non-obvious lines of code
|
||||||
|
|||||||
27
Makefile
27
Makefile
@@ -2,8 +2,10 @@ GOLANG_VERSION=1.13
|
|||||||
VERSION=snapshot
|
VERSION=snapshot
|
||||||
DOCKER_USER=
|
DOCKER_USER=
|
||||||
DOCKER_PASSWORD=
|
DOCKER_PASSWORD=
|
||||||
DOCKER_REPOSITORY=thecodingmachine
|
DOCKER_REGISTRY=thecodingmachine
|
||||||
GOLANGCI_LINT_VERSION=1.19.1
|
GOTENBERG_USER_GID=1001
|
||||||
|
GOTENBERG_USER_UID=1001
|
||||||
|
GOLANGCI_LINT_VERSION=1.20.1
|
||||||
CODE_COVERAGE=0
|
CODE_COVERAGE=0
|
||||||
TINI_VERSION=0.18.0
|
TINI_VERSION=0.18.0
|
||||||
MAXIMUM_WAIT_TIMEOUT=30.0
|
MAXIMUM_WAIT_TIMEOUT=30.0
|
||||||
@@ -15,16 +17,17 @@ DEFAULT_LISTEN_PORT=3000
|
|||||||
DISABLE_GOOGLE_CHROME=0
|
DISABLE_GOOGLE_CHROME=0
|
||||||
DISABLE_UNOCONV=0
|
DISABLE_UNOCONV=0
|
||||||
LOG_LEVEL=INFO
|
LOG_LEVEL=INFO
|
||||||
|
ROOT_PATH=/
|
||||||
DEFAULT_GOOGLE_CHROME_RPCC_BUFFER_SIZE=1048576
|
DEFAULT_GOOGLE_CHROME_RPCC_BUFFER_SIZE=1048576
|
||||||
|
|
||||||
# build the base Docker image.
|
# build the base Docker image.
|
||||||
base:
|
base:
|
||||||
docker build -t $(DOCKER_REPOSITORY)/gotenberg:base -f build/base/Dockerfile .
|
docker build --build-arg GOTENBERG_USER_GID=$(GOTENBERG_USER_GID) --build-arg GOTENBERG_USER_UID=$(GOTENBERG_USER_UID) -t $(DOCKER_REGISTRY)/gotenberg:base -f build/base/Dockerfile .
|
||||||
|
|
||||||
# build the workspace Docker image.
|
# build the workspace Docker image.
|
||||||
workspace:
|
workspace:
|
||||||
make base
|
make base
|
||||||
docker build --build-arg GOLANG_VERSION=$(GOLANG_VERSION) -t $(DOCKER_REPOSITORY)/gotenberg:workspace -f build/workspace/Dockerfile .
|
docker build --build-arg GOLANG_VERSION=$(GOLANG_VERSION) -t $(DOCKER_REGISTRY)/gotenberg:workspace -f build/workspace/Dockerfile .
|
||||||
|
|
||||||
# gofmt and goimports all go files.
|
# gofmt and goimports all go files.
|
||||||
fmt:
|
fmt:
|
||||||
@@ -34,30 +37,30 @@ fmt:
|
|||||||
# run all linters.
|
# run all linters.
|
||||||
lint:
|
lint:
|
||||||
make workspace
|
make workspace
|
||||||
docker build --build-arg GOLANGCI_LINT_VERSION=$(GOLANGCI_LINT_VERSION) -t $(DOCKER_REPOSITORY)/gotenberg:lint -f build/lint/Dockerfile .
|
docker build --build-arg GOLANGCI_LINT_VERSION=$(GOLANGCI_LINT_VERSION) -t $(DOCKER_REGISTRY)/gotenberg:lint -f build/lint/Dockerfile .
|
||||||
docker run --rm $(DOCKER_REPOSITORY)/gotenberg:lint
|
docker run --rm $(DOCKER_REGISTRY)/gotenberg:lint
|
||||||
|
|
||||||
# run all tests.
|
# run all tests.
|
||||||
tests:
|
tests:
|
||||||
make workspace
|
make workspace
|
||||||
./scripts/tests.sh $(DOCKER_REPOSITORY) $(CODE_COVERAGE)
|
./scripts/tests.sh $(DOCKER_REGISTRY) $(CODE_COVERAGE)
|
||||||
|
|
||||||
# generate documentation.
|
# generate documentation.
|
||||||
doc:
|
doc:
|
||||||
make workspace
|
make workspace
|
||||||
docker build -t $(DOCKER_REPOSITORY)/gotenberg:docs -f build/docs/Dockerfile .
|
docker build -t $(DOCKER_REGISTRY)/gotenberg:docs -f build/docs/Dockerfile .
|
||||||
docker run --rm -it -v "$(PWD):/gotenberg/docs" $(DOCKER_REPOSITORY)/gotenberg:docs
|
docker run --rm -it -v "$(PWD):/gotenberg/docs" $(DOCKER_REGISTRY)/gotenberg:docs
|
||||||
|
|
||||||
# build Gotenberg Docker image.
|
# build Gotenberg Docker image.
|
||||||
image:
|
image:
|
||||||
make workspace
|
make workspace
|
||||||
docker build --build-arg VERSION=$(VERSION) --build-arg TINI_VERSION=$(TINI_VERSION) -t $(DOCKER_REPOSITORY)/gotenberg:$(VERSION) -f build/package/Dockerfile .
|
docker build --build-arg VERSION=$(VERSION) --build-arg TINI_VERSION=$(TINI_VERSION) -t $(DOCKER_REGISTRY)/gotenberg:$(VERSION) -f build/package/Dockerfile .
|
||||||
|
|
||||||
# start the API using previously built Docker image.
|
# start the API using previously built Docker image.
|
||||||
gotenberg:
|
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 DEFAULT_GOOGLE_CHROME_RPCC_BUFFER_SIZE=$(DEFAULT_GOOGLE_CHROME_RPCC_BUFFER_SIZE) -p "$(DEFAULT_LISTEN_PORT):$(DEFAULT_LISTEN_PORT)" $(DOCKER_REPOSITORY)/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) -p "$(DEFAULT_LISTEN_PORT):$(DEFAULT_LISTEN_PORT)" $(DOCKER_REGISTRY)/gotenberg:$(VERSION)
|
||||||
|
|
||||||
# publish Gotenberg images according to version.
|
# publish Gotenberg images according to version.
|
||||||
publish:
|
publish:
|
||||||
make workspace
|
make workspace
|
||||||
./scripts/publish.sh $(GOLANG_VERSION) $(TINI_VERSION) $(DOCKER_REPOSITORY) $(VERSION) $(DOCKER_USER) $(DOCKER_PASSWORD)
|
./scripts/publish.sh $(GOLANG_VERSION) $(TINI_VERSION) $(DOCKER_REGISTRY) $(VERSION) $(DOCKER_USER) $(DOCKER_PASSWORD)
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://user-images.githubusercontent.com/8983173/50009948-84b01e00-ffb8-11e8-850b-fc240382c626.png" alt="Gotenberg logo" width="250" height="250" />
|
<img src="https://user-images.githubusercontent.com/8983173/69229423-ac731300-0b85-11ea-8c2e-2cc00ecdb269.PNG" alt="Gotenberg logo" width="250" height="250" />
|
||||||
</p>
|
</p>
|
||||||
<h3 align="center">Gotenberg</h3>
|
<h3 align="center">Gotenberg</h3>
|
||||||
<p align="center">A Docker-powered stateless API for converting HTML, Markdown and Office documents to PDF.</p>
|
<p align="center">A Docker-powered stateless API for converting HTML, Markdown and Office documents to PDF.</p>
|
||||||
@@ -39,4 +39,6 @@ to learn how to interact with it!
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Psst: TheCodingMachine is always looking for [talented coders](https://coders.thecodingmachine.com).
|
<p align="center">
|
||||||
|
<img src="https://user-images.githubusercontent.com/8983173/50009948-84b01e00-ffb8-11e8-850b-fc240382c626.png" alt="Gotenberg logo" width="150" height="150" />
|
||||||
|
</p>
|
||||||
|
|||||||
BIN
assets/logo.png
Normal file
BIN
assets/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.0 MiB |
BIN
assets/logo2.png
Normal file
BIN
assets/logo2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
@@ -104,7 +104,10 @@ COPY build/base/fonts.conf /etc/fonts/conf.d/100-gotenberg.conf
|
|||||||
# | non-root user.
|
# | non-root user.
|
||||||
# |
|
# |
|
||||||
|
|
||||||
RUN groupadd --gid 1001 gotenberg \
|
ARG GOTENBERG_USER_GID=1001
|
||||||
&& useradd --uid 1001 --gid gotenberg --shell /bin/bash --home /gotenberg --no-create-home gotenberg \
|
ARG GOTENBERG_USER_UID=1001
|
||||||
|
|
||||||
|
RUN groupadd --gid ${GOTENBERG_USER_GID} gotenberg \
|
||||||
|
&& useradd --uid ${GOTENBERG_USER_UID} --gid gotenberg --shell /bin/bash --home /gotenberg --no-create-home gotenberg \
|
||||||
&& mkdir /gotenberg \
|
&& mkdir /gotenberg \
|
||||||
&& chown gotenberg: /gotenberg
|
&& chown gotenberg: /gotenberg
|
||||||
@@ -4,8 +4,6 @@ title: Install
|
|||||||
|
|
||||||
Gotenberg is shipped within a Docker image.
|
Gotenberg is shipped within a Docker image.
|
||||||
|
|
||||||
> It uses a dedicated non-root user called `gotenberg` with uid and gid `1001`.
|
|
||||||
|
|
||||||
You may start it with:
|
You may start it with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -14,6 +12,23 @@ $ docker run --rm -p 3000:3000 thecodingmachine/gotenberg:6
|
|||||||
|
|
||||||
> The API will be available at [http://localhost:3000](http://localhost:3000).
|
> The API will be available at [http://localhost:3000](http://localhost:3000).
|
||||||
|
|
||||||
|
The image uses a dedicated non-root user called `gotenberg` with uid and gid `1001`.
|
||||||
|
|
||||||
|
If you wish to change those uid and gid, you will have to:
|
||||||
|
|
||||||
|
* clone the project
|
||||||
|
* re-build the image
|
||||||
|
* publish the image in your own Docker registry
|
||||||
|
|
||||||
|
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.2.0
|
||||||
|
```
|
||||||
|
|
||||||
|
> `master` branch is always up-to-date with the latest version of the API.
|
||||||
|
|
||||||
## Docker Compose
|
## Docker Compose
|
||||||
|
|
||||||
You may also add it in your Docker Compose stack:
|
You may also add it in your Docker Compose stack:
|
||||||
@@ -39,7 +54,7 @@ Make sure to provide enough memory and CPU requests (for instance `512Mi` and `0
|
|||||||
|
|
||||||
> The more resources are granted, the quicker will be the conversions.
|
> The more resources are granted, the quicker will be the conversions.
|
||||||
|
|
||||||
In the deployment specification of the pod, also specify the uid `1001` of the user `gotenberg`:
|
In the deployment specification of the pod, also specify the uid of the user `gotenberg`:
|
||||||
|
|
||||||
```
|
```
|
||||||
securityContext:
|
securityContext:
|
||||||
@@ -47,5 +62,9 @@ securityContext:
|
|||||||
runAsUser: 1001
|
runAsUser: 1001
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Cloud Run (Google Cloud)
|
||||||
|
|
||||||
|
If you're looking for cost savings, you might be interested by [Cloud Run](https://cloud.google.com/run).
|
||||||
|
|
||||||
In the following examples, we will assume your
|
In the following examples, we will assume your
|
||||||
Gotenberg API is available at [http://localhost:3000](http://localhost:3000).
|
Gotenberg API is available at [http://localhost:3000](http://localhost:3000).
|
||||||
|
|||||||
@@ -7,9 +7,11 @@ We provide clients in various languages for easing the interactions with the API
|
|||||||
## Go client
|
## Go client
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ go get -u github.com/thecodingmachine/gotenberg-go-client/v6
|
$ go get -u github.com/thecodingmachine/gotenberg-go-client/v7
|
||||||
```
|
```
|
||||||
|
|
||||||
|
See also the example from the [README](https://github.com/thecodingmachine/gotenberg-go-client/blob/master/README.md).
|
||||||
|
|
||||||
## PHP client
|
## PHP client
|
||||||
|
|
||||||
Unless your project already has a PSR7 `HttpClient`, install `php-http/guzzle6-adapter`:
|
Unless your project already has a PSR7 `HttpClient`, install `php-http/guzzle6-adapter`:
|
||||||
@@ -18,8 +20,15 @@ Unless your project already has a PSR7 `HttpClient`, install `php-http/guzzle6-a
|
|||||||
$ composer require php-http/guzzle6-adapter
|
$ composer require php-http/guzzle6-adapter
|
||||||
```
|
```
|
||||||
|
|
||||||
Then the PHP client:
|
Then the [PHP client](https://github.com/thecodingmachine/gotenberg-php-client):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ composer require thecodingmachine/gotenberg-php-client
|
$ composer require thecodingmachine/gotenberg-php-client
|
||||||
```
|
```
|
||||||
|
|
||||||
|
See also the example from the [README](https://github.com/thecodingmachine/gotenberg-php-client/blob/master/README.md).
|
||||||
|
|
||||||
|
## Community clients
|
||||||
|
|
||||||
|
* [JavaScript/TypeScript client](https://github.com/yumauri/gotenberg-js-client) by [yumauri](https://github.com/yumauri)
|
||||||
|
* [C# client](https://github.com/ChangemakerStudios/GotenbergSharpApiClient) by [ChangemakerStudios](https://github.com/ChangemakerStudios)
|
||||||
|
|||||||
@@ -23,6 +23,18 @@ You may customize this value with the environment variable `DEFAULT_LISTEN_PORT`
|
|||||||
|
|
||||||
This environment variable accepts any string that can be turned into a port number.
|
This environment variable accepts any string that can be turned into a port number.
|
||||||
|
|
||||||
|
## Root path
|
||||||
|
|
||||||
|
By default, the API root path is `/`.
|
||||||
|
|
||||||
|
You may customize this value with the environment variable `ROOT_PATH`.
|
||||||
|
|
||||||
|
This environment variable accepts a string starting and ending with `/`.
|
||||||
|
|
||||||
|
For instance, `/gotenberg/` is a valid value while `gotenberg` is not.
|
||||||
|
|
||||||
|
> This is useful if you wish to do service discovery via URL paths.
|
||||||
|
|
||||||
## Disable Google Chrome
|
## Disable Google Chrome
|
||||||
|
|
||||||
In order to save some resources, the Gotenberg image accepts the environment variable `DISABLE_GOOGLE_CHROME`
|
In order to save some resources, the Gotenberg image accepts the environment variable `DISABLE_GOOGLE_CHROME`
|
||||||
|
|||||||
@@ -39,14 +39,13 @@ $ curl --request POST \
|
|||||||
### Go
|
### Go
|
||||||
|
|
||||||
```golang
|
```golang
|
||||||
import "github.com/thecodingmachine/gotenberg-go-client/v6"
|
import "github.com/thecodingmachine/gotenberg-go-client/v7"
|
||||||
|
|
||||||
func main() {
|
c := &gotenberg.Client{Hostname: "http://localhost:3000"}
|
||||||
c := &gotenberg.Client{Hostname: "http://localhost:3000"}
|
index, _ := gotenberg.NewDocumentFromPath("index.html", "/path/to/file")
|
||||||
req, _ := gotenberg.NewHTMLRequest("index.html")
|
req := gotenberg.NewHTMLRequest(index)
|
||||||
dest := "result.pdf"
|
dest := "result.pdf"
|
||||||
c.Store(req, dest)
|
c.Store(req, dest)
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### PHP
|
### PHP
|
||||||
@@ -57,10 +56,10 @@ use TheCodingMachine\Gotenberg\DocumentFactory;
|
|||||||
use TheCodingMachine\Gotenberg\HTMLRequest;
|
use TheCodingMachine\Gotenberg\HTMLRequest;
|
||||||
|
|
||||||
$client = new Client('http://localhost:3000', new \Http\Adapter\Guzzle6\Client());
|
$client = new Client('http://localhost:3000', new \Http\Adapter\Guzzle6\Client());
|
||||||
$index = DocumentFactory::makeFromPath('index.html', 'index.html');
|
$index = DocumentFactory::makeFromPath('index.html', '/path/to/file');
|
||||||
$request = new HTMLRequest($index);
|
$request = new HTMLRequest($index);
|
||||||
$dirPath = "/foo";
|
$dest = 'result.pdf';
|
||||||
$filename = $client->store($request, $dirPath);
|
$client->store($request, $dest);
|
||||||
```
|
```
|
||||||
|
|
||||||
## Header and footer
|
## Header and footer
|
||||||
@@ -103,6 +102,7 @@ There are some limitations:
|
|||||||
* `footer.html` CSS properties override the ones from `header.html`
|
* `footer.html` CSS properties override the ones from `header.html`
|
||||||
* only fonts installed in the Docker image are loaded (see the [fonts section](#fonts))
|
* only fonts installed in the Docker image are loaded (see the [fonts section](#fonts))
|
||||||
* images only work using a `base64` encoded source (`<img src="data:image/png;base64, iVBORw0K... />`)
|
* images only work using a `base64` encoded source (`<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
|
||||||
|
|
||||||
### cURL
|
### cURL
|
||||||
|
|
||||||
@@ -119,16 +119,17 @@ $ curl --request POST \
|
|||||||
### Go
|
### Go
|
||||||
|
|
||||||
```golang
|
```golang
|
||||||
import "github.com/thecodingmachine/gotenberg-go-client/v6"
|
import "github.com/thecodingmachine/gotenberg-go-client/v7"
|
||||||
|
|
||||||
func main() {
|
c := &gotenberg.Client{Hostname: "http://localhost:3000"}
|
||||||
c := &gotenberg.Client{Hostname: "http://localhost:3000"}
|
index, _ := gotenberg.NewDocumentFromPath("index.html", "/path/to/file")
|
||||||
req, _ := gotenberg.NewHTMLRequest("index.html")
|
header, _ := gotenberg.NewDocumentFromPath("header.html", "/path/to/file")
|
||||||
req.Header("header.html")
|
footer, _ := gotenberg.NewDocumentFromPath("footer.html", "/path/to/file")
|
||||||
req.Footer("footer.html")
|
req := gotenberg.NewHTMLRequest(index)
|
||||||
dest := "result.pdf"
|
req.Header(header)
|
||||||
c.Store(req, dest)
|
req.Footer(footer)
|
||||||
}
|
dest := "result.pdf"
|
||||||
|
c.Store(req, dest)
|
||||||
```
|
```
|
||||||
|
|
||||||
### PHP
|
### PHP
|
||||||
@@ -139,14 +140,14 @@ use TheCodingMachine\Gotenberg\DocumentFactory;
|
|||||||
use TheCodingMachine\Gotenberg\HTMLRequest;
|
use TheCodingMachine\Gotenberg\HTMLRequest;
|
||||||
|
|
||||||
$client = new Client('http://localhost:3000', new \Http\Adapter\Guzzle6\Client());
|
$client = new Client('http://localhost:3000', new \Http\Adapter\Guzzle6\Client());
|
||||||
$index = DocumentFactory::makeFromPath('index.html', 'index.html');
|
$index = DocumentFactory::makeFromPath('index.html', '/path/to/file');
|
||||||
$header = DocumentFactory::makeFromPath('header.html', 'header.html');
|
$header = DocumentFactory::makeFromPath('header.html', '/path/to/file');
|
||||||
$footer = DocumentFactory::makeFromPath('footer.html', 'footer.html');
|
$footer = DocumentFactory::makeFromPath('footer.html', '/path/to/file');
|
||||||
$request = new HTMLRequest($index);
|
$request = new HTMLRequest($index);
|
||||||
$request->setHeader($header);
|
$request->setHeader($header);
|
||||||
$request->setFooter($footer);
|
$request->setFooter($footer);
|
||||||
$dirPath = "/foo";
|
$dest = 'result.pdf';
|
||||||
$filename = $client->store($request, $dirPath);
|
$client->store($request, $dest);
|
||||||
```
|
```
|
||||||
|
|
||||||
## Assets
|
## Assets
|
||||||
@@ -209,15 +210,17 @@ $ curl --request POST \
|
|||||||
### Go
|
### Go
|
||||||
|
|
||||||
```golang
|
```golang
|
||||||
import "github.com/thecodingmachine/gotenberg-go-client/v6"
|
import "github.com/thecodingmachine/gotenberg-go-client/v7"
|
||||||
|
|
||||||
func main() {
|
c := &gotenberg.Client{Hostname: "http://localhost:3000"}
|
||||||
c := &gotenberg.Client{Hostname: "http://localhost:3000"}
|
index, _ := gotenberg.NewDocumentFromPath("index.html", "/path/to/file")
|
||||||
req, _ := gotenberg.NewHTMLRequest("index.html")
|
style, _ := gotenberg.NewDocumentFromPath("style.css", "/path/to/file")
|
||||||
req.Assets("font.woff", "img.gif", "style.css")
|
img, _ := gotenberg.NewDocumentFromPath("img.png", "/path/to/file")
|
||||||
dest := "result.pdf"
|
font, _ := gotenberg.NewDocumentFromPath("font.woff", "/path/to/file")
|
||||||
c.Store(req, dest)
|
req := gotenberg.NewHTMLRequest(index)
|
||||||
}
|
req.Assets(style, img, font)
|
||||||
|
dest := "result.pdf"
|
||||||
|
c.Store(req, dest)
|
||||||
```
|
```
|
||||||
|
|
||||||
### PHP
|
### PHP
|
||||||
@@ -228,23 +231,23 @@ use TheCodingMachine\Gotenberg\DocumentFactory;
|
|||||||
use TheCodingMachine\Gotenberg\HTMLRequest;
|
use TheCodingMachine\Gotenberg\HTMLRequest;
|
||||||
|
|
||||||
$client = new Client('http://localhost:3000', new \Http\Adapter\Guzzle6\Client());
|
$client = new Client('http://localhost:3000', new \Http\Adapter\Guzzle6\Client());
|
||||||
$index = DocumentFactory::makeFromPath('index.html', 'index.html');
|
$index = DocumentFactory::makeFromPath('index.html', '/path/to/file');
|
||||||
$assets = [
|
$assets = [
|
||||||
DocumentFactory::makeFromPath('style.css', 'style.css'),
|
DocumentFactory::makeFromPath('style.css', '/path/to/file'),
|
||||||
DocumentFactory::makeFromPath('img.png', 'img.png'),
|
DocumentFactory::makeFromPath('img.png', '/path/to/file'),
|
||||||
DocumentFactory::makeFromPath('font.woff', 'font.woff'),
|
DocumentFactory::makeFromPath('font.woff', '/path/to/file'),
|
||||||
];
|
];
|
||||||
$request = new HTMLRequest($index);
|
$request = new HTMLRequest($index);
|
||||||
$request->setAssets($assets);
|
$request->setAssets($assets);
|
||||||
$dest = "result.pdf";
|
$dest = 'result.pdf';
|
||||||
$client->store($request, $dest);
|
$client->store($request, $dest);
|
||||||
```
|
```
|
||||||
|
|
||||||
## Paper size, margins, orientation
|
## Paper size, margins, orientation, scaling
|
||||||
|
|
||||||
You may also customize the resulting PDF format.
|
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.
|
> Paper size and margins have to be provided in `inches`. Same for margins.
|
||||||
|
|
||||||
@@ -262,23 +265,24 @@ $ curl --request POST \
|
|||||||
--form marginLeft=0 \
|
--form marginLeft=0 \
|
||||||
--form marginRight=0 \
|
--form marginRight=0 \
|
||||||
--form landscape=true \
|
--form landscape=true \
|
||||||
|
--form scale=0.75 \
|
||||||
-o result.pdf
|
-o result.pdf
|
||||||
```
|
```
|
||||||
|
|
||||||
### Go
|
### Go
|
||||||
|
|
||||||
```golang
|
```golang
|
||||||
import "github.com/thecodingmachine/gotenberg-go-client/v6"
|
import "github.com/thecodingmachine/gotenberg-go-client/v7"
|
||||||
|
|
||||||
func main() {
|
c := &gotenberg.Client{Hostname: "http://localhost:3000"}
|
||||||
c := &gotenberg.Client{Hostname: "http://localhost:3000"}
|
index, _ := gotenberg.NewDocumentFromPath("index.html", "/path/to/file")
|
||||||
req, _ := gotenberg.NewHTMLRequest("index.html")
|
req := gotenberg.NewHTMLRequest(index)
|
||||||
req.PaperSize(gotenberg.A4)
|
req.PaperSize(gotenberg.A4)
|
||||||
req.Margins(gotenberg.NoMargins)
|
req.Margins(gotenberg.NoMargins)
|
||||||
req.Landscape(true)
|
req.Landscape(true)
|
||||||
dest := "result.pdf"
|
req.Scale(0.75)
|
||||||
c.Store(req, dest)
|
dest := "result.pdf"
|
||||||
}
|
c.Store(req, dest)
|
||||||
```
|
```
|
||||||
|
|
||||||
### PHP
|
### PHP
|
||||||
@@ -290,12 +294,60 @@ use TheCodingMachine\Gotenberg\HTMLRequest;
|
|||||||
use TheCodingMachine\Gotenberg\Request;
|
use TheCodingMachine\Gotenberg\Request;
|
||||||
|
|
||||||
$client = new Client('http://localhost:3000', new \Http\Adapter\Guzzle6\Client());
|
$client = new Client('http://localhost:3000', new \Http\Adapter\Guzzle6\Client());
|
||||||
$index = DocumentFactory::makeFromPath('index.html', 'index.html');
|
$index = DocumentFactory::makeFromPath('index.html', '/path/to/file');
|
||||||
$request = new HTMLRequest($index);
|
$request = new HTMLRequest($index);
|
||||||
$request->setPaperSize(Request::A4);
|
$request->setPaperSize(Request::A4);
|
||||||
$request->setMargins(Request::NO_MARGINS);
|
$request->setMargins(Request::NO_MARGINS);
|
||||||
$request->setLandscape(true);
|
$request->setLandscape(true);
|
||||||
$dest = "result.pdf";
|
$request->setScale(0.75);
|
||||||
|
$dest = 'result.pdf';
|
||||||
|
$client->store($request, $dest);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Page ranges
|
||||||
|
|
||||||
|
You may specify the page ranges to convert.
|
||||||
|
|
||||||
|
The format is the same as the one from the print options
|
||||||
|
of Google Chrome, e.g. `1-5,8,11-13`.
|
||||||
|
|
||||||
|
### cURL
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ curl --request POST \
|
||||||
|
--url http://localhost:3000/convert/html \
|
||||||
|
--header 'Content-Type: multipart/form-data' \
|
||||||
|
--form files=@index.html \
|
||||||
|
--form pageRanges='1-3,5' \
|
||||||
|
-o result.pdf
|
||||||
|
```
|
||||||
|
|
||||||
|
### Go
|
||||||
|
|
||||||
|
```golang
|
||||||
|
import "github.com/thecodingmachine/gotenberg-go-client/v7"
|
||||||
|
|
||||||
|
c := &gotenberg.Client{Hostname: "http://localhost:3000"}
|
||||||
|
index, _ := gotenberg.NewDocumentFromPath("index.html", "/path/to/file")
|
||||||
|
req := gotenberg.NewHTMLRequest(index)
|
||||||
|
req.PageRanges("1-3,5")
|
||||||
|
dest := "result.pdf"
|
||||||
|
c.Store(req, dest)
|
||||||
|
```
|
||||||
|
|
||||||
|
### PHP
|
||||||
|
|
||||||
|
```php
|
||||||
|
use TheCodingMachine\Gotenberg\Client;
|
||||||
|
use TheCodingMachine\Gotenberg\DocumentFactory;
|
||||||
|
use TheCodingMachine\Gotenberg\HTMLRequest;
|
||||||
|
use TheCodingMachine\Gotenberg\Request;
|
||||||
|
|
||||||
|
$client = new Client('http://localhost:3000', new \Http\Adapter\Guzzle6\Client());
|
||||||
|
$index = DocumentFactory::makeFromPath('index.html', '/path/to/file');
|
||||||
|
$request = new HTMLRequest($index);
|
||||||
|
$request->setPageRanges('1-3,5');
|
||||||
|
$dest = 'result.pdf';
|
||||||
$client->store($request, $dest);
|
$client->store($request, $dest);
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -321,15 +373,14 @@ $ curl --request POST \
|
|||||||
### Go
|
### Go
|
||||||
|
|
||||||
```golang
|
```golang
|
||||||
import "github.com/thecodingmachine/gotenberg-go-client/v6"
|
import "github.com/thecodingmachine/gotenberg-go-client/v7"
|
||||||
|
|
||||||
func main() {
|
c := &gotenberg.Client{Hostname: "http://localhost:3000"}
|
||||||
c := &gotenberg.Client{Hostname: "http://localhost:3000"}
|
index, _ := gotenberg.NewDocumentFromPath("index.html", "/path/to/file")
|
||||||
req, _ := gotenberg.NewHTMLRequest("index.html")
|
req := gotenberg.NewHTMLRequest(index)
|
||||||
req.WaitDelay(5.5)
|
req.WaitDelay(5.5)
|
||||||
dest := "result.pdf"
|
dest := "result.pdf"
|
||||||
c.Store(req, dest)
|
c.Store(req, dest)
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### PHP
|
### PHP
|
||||||
@@ -341,10 +392,10 @@ use TheCodingMachine\Gotenberg\HTMLRequest;
|
|||||||
use TheCodingMachine\Gotenberg\Request;
|
use TheCodingMachine\Gotenberg\Request;
|
||||||
|
|
||||||
$client = new Client('http://localhost:3000', new \Http\Adapter\Guzzle6\Client());
|
$client = new Client('http://localhost:3000', new \Http\Adapter\Guzzle6\Client());
|
||||||
$index = DocumentFactory::makeFromPath('index.html', 'index.html');
|
$index = DocumentFactory::makeFromPath('index.html', '/path/to/file');
|
||||||
$request = new HTMLRequest($index);
|
$request = new HTMLRequest($index);
|
||||||
$request->setWaitDelay(5.5);
|
$request->setWaitDelay(5.5);
|
||||||
$dest = "result.pdf";
|
$dest = 'result.pdf';
|
||||||
$client->store($request, $dest);
|
$client->store($request, $dest);
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -373,15 +424,14 @@ $ curl --request POST \
|
|||||||
### Go
|
### Go
|
||||||
|
|
||||||
```golang
|
```golang
|
||||||
import "github.com/thecodingmachine/gotenberg-go-client/v6"
|
import "github.com/thecodingmachine/gotenberg-go-client/v7"
|
||||||
|
|
||||||
func main() {
|
c := &gotenberg.Client{Hostname: "http://localhost:3000"}
|
||||||
c := &gotenberg.Client{Hostname: "http://localhost:3000"}
|
index, _ := gotenberg.NewDocumentFromPath("index.html", "/path/to/file")
|
||||||
req, _ := gotenberg.NewHTMLRequest("index.html")
|
req := gotenberg.NewHTMLRequest(index)
|
||||||
req.GoogleChromeRpccBufferSize(1048576)
|
req.GoogleChromeRpccBufferSize(1048576)
|
||||||
dest := "result.pdf"
|
dest := "result.pdf"
|
||||||
c.Store(req, dest)
|
c.Store(req, dest)
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### PHP
|
### PHP
|
||||||
@@ -393,9 +443,9 @@ use TheCodingMachine\Gotenberg\HTMLRequest;
|
|||||||
use TheCodingMachine\Gotenberg\Request;
|
use TheCodingMachine\Gotenberg\Request;
|
||||||
|
|
||||||
$client = new Client('http://localhost:3000', new \Http\Adapter\Guzzle6\Client());
|
$client = new Client('http://localhost:3000', new \Http\Adapter\Guzzle6\Client());
|
||||||
$index = DocumentFactory::makeFromPath('index.html', 'index.html');
|
$index = DocumentFactory::makeFromPath('index.html', '/path/to/file');
|
||||||
$request = new HTMLRequest($index);
|
$request = new HTMLRequest($index);
|
||||||
$request->setGoogleChromeRpccBufferSize(1048576);
|
$request->setGoogleChromeRpccBufferSize(1048576);
|
||||||
$dest = "result.pdf";
|
$dest = 'result.pdf';
|
||||||
$client->store($request, $dest);
|
$client->store($request, $dest);
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -31,15 +31,13 @@ $ curl --request POST \
|
|||||||
### Go
|
### Go
|
||||||
|
|
||||||
```golang
|
```golang
|
||||||
import "github.com/thecodingmachine/gotenberg-go-client/v6"
|
import "github.com/thecodingmachine/gotenberg-go-client/v7"
|
||||||
|
|
||||||
func main() {
|
c := &gotenberg.Client{Hostname: "http://localhost:3000"}
|
||||||
c := &gotenberg.Client{Hostname: "http://localhost:3000"}
|
req := gotenberg.NewURLRequest("https://google.com")
|
||||||
req := gotenberg.NewURLRequest("https://google.com")
|
req.Margins(gotenberg.NoMargins)
|
||||||
req.Margins(gotenberg.NoMargins)
|
dest := "result.pdf"
|
||||||
dest := "result.pdf"
|
c.Store(req, dest)
|
||||||
c.Store(req, dest)
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### PHP
|
### PHP
|
||||||
@@ -51,6 +49,55 @@ use TheCodingMachine\Gotenberg\URLRequest;
|
|||||||
$client = new Client('http://localhost:3000', new \Http\Adapter\Guzzle6\Client());
|
$client = new Client('http://localhost:3000', new \Http\Adapter\Guzzle6\Client());
|
||||||
$request = new URLRequest('https://google.com');
|
$request = new URLRequest('https://google.com');
|
||||||
$request->setMargins(Request::NO_MARGINS);
|
$request->setMargins(Request::NO_MARGINS);
|
||||||
$dest = "result.pdf";
|
$dest = 'result.pdf';
|
||||||
|
$client->store($request, $dest);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Custom HTTP headers
|
||||||
|
|
||||||
|
You may send your own HTTP headers to the `remoteURL`.
|
||||||
|
|
||||||
|
For instance, by adding the HTTP header `Gotenberg-Remoteurl-Your-Header` to your request,
|
||||||
|
the API will send a request to the `remoteURL` with the HTTP header `Your-Header`.
|
||||||
|
|
||||||
|
> **Attention:** the API uses a canonical format for the HTTP headers:
|
||||||
|
> it transforms the first
|
||||||
|
> letter and any letter following a hyphen to upper case;
|
||||||
|
> the rest are converted to lowercase. For example, the
|
||||||
|
> canonical key for `accept-encoding` is `Accept-Encoding`.
|
||||||
|
|
||||||
|
### cURL
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ curl --request POST \
|
||||||
|
--url http://localhost:3000/convert/url \
|
||||||
|
--header 'Content-Type: multipart/form-data' \
|
||||||
|
--header 'Gotenberg-Remoteurl-Your-Header: Foo' \
|
||||||
|
--form remoteURL=https://google.com \
|
||||||
|
-o result.pdf
|
||||||
|
```
|
||||||
|
|
||||||
|
### Go
|
||||||
|
|
||||||
|
```golang
|
||||||
|
import "github.com/thecodingmachine/gotenberg-go-client/v7"
|
||||||
|
|
||||||
|
c := &gotenberg.Client{Hostname: "http://localhost:3000"}
|
||||||
|
req := gotenberg.NewURLRequest("https://google.com")
|
||||||
|
req.AddRemoteURLHTTPHeader("Your-Header", "Foo")
|
||||||
|
dest := "result.pdf"
|
||||||
|
c.Store(req, dest)
|
||||||
|
```
|
||||||
|
|
||||||
|
### PHP
|
||||||
|
|
||||||
|
```php
|
||||||
|
use TheCodingMachine\Gotenberg\Client;
|
||||||
|
use TheCodingMachine\Gotenberg\URLRequest;
|
||||||
|
|
||||||
|
$client = new Client('http://localhost:3000', new \Http\Adapter\Guzzle6\Client());
|
||||||
|
$request = new URLRequest('https://google.com');
|
||||||
|
$request->addRemoteURLHTTPHeader('Your-Header', 'Foo')
|
||||||
|
$dest = 'result.pdf';
|
||||||
$client->store($request, $dest);
|
$client->store($request, $dest);
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -42,14 +42,14 @@ $ curl --request POST \
|
|||||||
### Go
|
### Go
|
||||||
|
|
||||||
```golang
|
```golang
|
||||||
import "github.com/thecodingmachine/gotenberg-go-client/v6"
|
import "github.com/thecodingmachine/gotenberg-go-client/v7"
|
||||||
|
|
||||||
func main() {
|
c := &gotenberg.Client{Hostname: "http://localhost:3000"}
|
||||||
c := &gotenberg.Client{Hostname: "http://localhost:3000"}
|
index, _ := gotenberg.NewDocumentFromPath("index.html", "/path/to/file")
|
||||||
req, _ := gotenberg.NewMarkdownRequest("index.html", "file.md")
|
markdown, _ := gotenberg.NewDocumentFromPath("file.md", "/path/to/file")
|
||||||
dest := "result.pdf"
|
req := gotenberg.NewMarkdownRequest(index, markdown)
|
||||||
c.Store(req, dest)
|
dest := "result.pdf"
|
||||||
}
|
c.Store(req, dest)
|
||||||
```
|
```
|
||||||
|
|
||||||
### PHP
|
### PHP
|
||||||
@@ -60,11 +60,11 @@ use TheCodingMachine\Gotenberg\DocumentFactory;
|
|||||||
use TheCodingMachine\Gotenberg\MarkdownRequest;
|
use TheCodingMachine\Gotenberg\MarkdownRequest;
|
||||||
|
|
||||||
$client = new Client('http://localhost:3000', new \Http\Adapter\Guzzle6\Client());
|
$client = new Client('http://localhost:3000', new \Http\Adapter\Guzzle6\Client());
|
||||||
$index = DocumentFactory::makeFromPath('index.html', 'index.html');
|
$index = DocumentFactory::makeFromPath('index.html', '/path/to/file');
|
||||||
$markdowns = [
|
$markdowns = [
|
||||||
DocumentFactory::makeFromPath('file.md', 'file.md'),
|
DocumentFactory::makeFromPath('file.md', '/path/to/file'),
|
||||||
];
|
];
|
||||||
$request = new MarkdownRequest($index, $markdowns);
|
$request = new MarkdownRequest($index, $markdowns);
|
||||||
$dest = "result.pdf";
|
$dest = 'result.pdf';
|
||||||
$client->store($request, $dest);
|
$client->store($request, $dest);
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -41,14 +41,14 @@ $ curl --request POST \
|
|||||||
### Go
|
### Go
|
||||||
|
|
||||||
```golang
|
```golang
|
||||||
import "github.com/thecodingmachine/gotenberg-go-client/v6"
|
import "github.com/thecodingmachine/gotenberg-go-client/v7"
|
||||||
|
|
||||||
func main() {
|
c := &gotenberg.Client{Hostname: "http://localhost:3000"}
|
||||||
c := &gotenberg.Client{Hostname: "http://localhost:3000"}
|
doc, _ := gotenberg.NewDocumentFromPath("document.docx", "/path/to/file")
|
||||||
req, _ := gotenberg.NewOfficeRequest("document.docx", "document2.docx")
|
doc2, _ := gotenberg.NewDocumentFromPath("document2.docx", "/path/to/file")
|
||||||
dest := "result.pdf"
|
req := gotenberg.NewOfficeRequest(doc, doc2)
|
||||||
c.Store(req, dest)
|
dest := "result.pdf"
|
||||||
}
|
c.Store(req, dest)
|
||||||
```
|
```
|
||||||
|
|
||||||
### PHP
|
### PHP
|
||||||
@@ -60,11 +60,11 @@ use TheCodingMachine\Gotenberg\OfficeRequest;
|
|||||||
|
|
||||||
$client = new Client('http://localhost:3000', new \Http\Adapter\Guzzle6\Client());
|
$client = new Client('http://localhost:3000', new \Http\Adapter\Guzzle6\Client());
|
||||||
$files = [
|
$files = [
|
||||||
DocumentFactory::makeFromPath('document.docx', 'document.docx'),
|
DocumentFactory::makeFromPath('document.docx', '/path/to/file'),
|
||||||
DocumentFactory::makeFromPath('document2.docx', 'document2.docx'),
|
DocumentFactory::makeFromPath('document2.docx', '/path/to/file'),
|
||||||
];
|
];
|
||||||
$request = new OfficeRequest($files);
|
$request = new OfficeRequest($files);
|
||||||
$dest = "result.pdf";
|
$dest = 'result.pdf';
|
||||||
$client->store($request, $dest);
|
$client->store($request, $dest);
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -88,15 +88,14 @@ $ curl --request POST \
|
|||||||
### Go
|
### Go
|
||||||
|
|
||||||
```golang
|
```golang
|
||||||
import "github.com/thecodingmachine/gotenberg-go-client/v6"
|
import "github.com/thecodingmachine/gotenberg-go-client/v7"
|
||||||
|
|
||||||
func main() {
|
c := &gotenberg.Client{Hostname: "http://localhost:3000"}
|
||||||
c := &gotenberg.Client{Hostname: "http://localhost:3000"}
|
doc, _ := gotenberg.NewDocumentFromPath("document.docx", "/path/to/file")
|
||||||
req, _ := gotenberg.NewOfficeRequest("document.docx")
|
req := gotenberg.NewOfficeRequest(doc)
|
||||||
req.Landscape(true)
|
req.Landscape(true)
|
||||||
dest := "result.pdf"
|
dest := "result.pdf"
|
||||||
c.Store(req, dest)
|
c.Store(req, dest)
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### PHP
|
### PHP
|
||||||
@@ -108,10 +107,61 @@ use TheCodingMachine\Gotenberg\OfficeRequest;
|
|||||||
|
|
||||||
$client = new Client('http://localhost:3000', new \Http\Adapter\Guzzle6\Client());
|
$client = new Client('http://localhost:3000', new \Http\Adapter\Guzzle6\Client());
|
||||||
$files = [
|
$files = [
|
||||||
DocumentFactory::makeFromPath('document.docx', 'document.docx'),
|
DocumentFactory::makeFromPath('document.docx', '/path/to/file'),
|
||||||
];
|
];
|
||||||
$request = new OfficeRequest($files);
|
$request = new OfficeRequest($files);
|
||||||
$request->setLandscape(true);
|
$request->setLandscape(true);
|
||||||
$dest = "result.pdf";
|
$dest = 'result.pdf';
|
||||||
|
$client->store($request, $dest);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Page ranges
|
||||||
|
|
||||||
|
You may specify the page ranges to convert.
|
||||||
|
|
||||||
|
The 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.
|
||||||
|
|
||||||
|
### cURL
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ curl --request POST \
|
||||||
|
--url http://localhost:3000/convert/office \
|
||||||
|
--header 'Content-Type: multipart/form-data' \
|
||||||
|
--form files=@document.docx \
|
||||||
|
--form pageRanges='1-3' \
|
||||||
|
-o result.pdf
|
||||||
|
```
|
||||||
|
|
||||||
|
### Go
|
||||||
|
|
||||||
|
```golang
|
||||||
|
import "github.com/thecodingmachine/gotenberg-go-client/v7"
|
||||||
|
|
||||||
|
c := &gotenberg.Client{Hostname: "http://localhost:3000"}
|
||||||
|
doc, _ := gotenberg.NewDocumentFromPath("document.docx", "/path/to/file")
|
||||||
|
req := gotenberg.NewOfficeRequest(doc)
|
||||||
|
req.PageRanges("1-3")
|
||||||
|
dest := "result.pdf"
|
||||||
|
c.Store(req, dest)
|
||||||
|
```
|
||||||
|
|
||||||
|
### PHP
|
||||||
|
|
||||||
|
```php
|
||||||
|
use TheCodingMachine\Gotenberg\Client;
|
||||||
|
use TheCodingMachine\Gotenberg\DocumentFactory;
|
||||||
|
use TheCodingMachine\Gotenberg\OfficeRequest;
|
||||||
|
|
||||||
|
$client = new Client('http://localhost:3000', new \Http\Adapter\Guzzle6\Client());
|
||||||
|
$files = [
|
||||||
|
DocumentFactory::makeFromPath('document.docx', '/path/to/file'),
|
||||||
|
];
|
||||||
|
$request = new OfficeRequest($files);
|
||||||
|
$request->setPageRanges('1-3');
|
||||||
|
$dest = 'result.pdf';
|
||||||
$client->store($request, $dest);
|
$client->store($request, $dest);
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -27,14 +27,14 @@ $ curl --request POST \
|
|||||||
### Go
|
### Go
|
||||||
|
|
||||||
```golang
|
```golang
|
||||||
import "github.com/thecodingmachine/gotenberg-go-client/v6"
|
import "github.com/thecodingmachine/gotenberg-go-client/v7"
|
||||||
|
|
||||||
func main() {
|
c := &gotenberg.Client{Hostname: "http://localhost:3000"}
|
||||||
c := &gotenberg.Client{Hostname: "http://localhost:3000"}
|
pdf, _ := gotenberg.NewDocumentFromPath("file.pdf", "/path/to/file")
|
||||||
req, _ := gotenberg.NewMergeRequest("file.pdf", "file2.pdf")
|
pdf2, _ := gotenberg.NewDocumentFromPath("file2.pdf", "/path/to/file")
|
||||||
dest := "result.pdf"
|
req := gotenberg.NewMergeRequest(pdf, pdf2)
|
||||||
c.Store(req, dest)
|
dest := "result.pdf"
|
||||||
}
|
c.Store(req, dest)
|
||||||
```
|
```
|
||||||
|
|
||||||
### PHP
|
### PHP
|
||||||
@@ -46,10 +46,10 @@ use TheCodingMachine\Gotenberg\MergeRequest;
|
|||||||
|
|
||||||
$client = new Client('http://localhost:3000', new \Http\Adapter\Guzzle6\Client());
|
$client = new Client('http://localhost:3000', new \Http\Adapter\Guzzle6\Client());
|
||||||
$files = [
|
$files = [
|
||||||
DocumentFactory::makeFromPath('file.pdf', 'file.pdf'),
|
DocumentFactory::makeFromPath('file.pdf', '/path/to/file'),
|
||||||
DocumentFactory::makeFromPath('file2.pdf', 'file2.pdf'),
|
DocumentFactory::makeFromPath('file2.pdf', '/path/to/file'),
|
||||||
];
|
];
|
||||||
$request = new MergeRequest($files);
|
$request = new MergeRequest($files);
|
||||||
$dest = "result.pdf";
|
$dest = 'result.pdf';
|
||||||
$client->store($request, $dest);
|
$client->store($request, $dest);
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -26,14 +26,13 @@ $ curl --request POST \
|
|||||||
### Go
|
### Go
|
||||||
|
|
||||||
```golang
|
```golang
|
||||||
import "github.com/thecodingmachine/gotenberg-go-client/v6"
|
import "github.com/thecodingmachine/gotenberg-go-client/v7"
|
||||||
|
|
||||||
func main() {
|
c := &gotenberg.Client{Hostname: "http://localhost:3000"}
|
||||||
c := &gotenberg.Client{Hostname: "http://localhost:3000"}
|
index, _ := gotenberg.NewDocumentFromPath("index.html", "/path/to/file")
|
||||||
req, _ := gotenberg.NewHTMLRequest("index.html")
|
req := gotenberg.NewHTMLRequest(index)
|
||||||
req.WaitTimeout(2.5)
|
req.WaitTimeout(2.5)
|
||||||
resp, _ := c.Post(req)
|
resp, _ := c.Post(req)
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### PHP
|
### PHP
|
||||||
@@ -45,9 +44,9 @@ use TheCodingMachine\Gotenberg\HTMLRequest;
|
|||||||
use TheCodingMachine\Gotenberg\Request;
|
use TheCodingMachine\Gotenberg\Request;
|
||||||
|
|
||||||
$client = new Client('http://localhost:3000', new \Http\Adapter\Guzzle6\Client());
|
$client = new Client('http://localhost:3000', new \Http\Adapter\Guzzle6\Client());
|
||||||
$index = DocumentFactory::makeFromPath('index.html', 'index.html');
|
$index = DocumentFactory::makeFromPath('index.html', '/path/to/file');
|
||||||
$request = new HTMLRequest($index);
|
$request = new HTMLRequest($index);
|
||||||
$request->setWaitTimeout(2.5);
|
$request->setWaitTimeout(2.5);
|
||||||
$dest = "result.pdf";
|
$dest = 'result.pdf';
|
||||||
$client->store($request, $dest);
|
$client->store($request, $dest);
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -24,14 +24,13 @@ $ curl --request POST \
|
|||||||
### Go
|
### Go
|
||||||
|
|
||||||
```golang
|
```golang
|
||||||
import "github.com/thecodingmachine/gotenberg-go-client/v6"
|
import "github.com/thecodingmachine/gotenberg-go-client/v7"
|
||||||
|
|
||||||
func main() {
|
c := &gotenberg.Client{Hostname: "http://localhost:3000"}
|
||||||
c := &gotenberg.Client{Hostname: "http://localhost:3000"}
|
index, _ := gotenberg.NewDocumentFromPath("index.html", "/path/to/file")
|
||||||
req, _ := gotenberg.NewHTMLRequest("index.html")
|
req := gotenberg.NewHTMLRequest(index)
|
||||||
req.WebhookURL("http://myapp.com/webhook/")
|
req.WebhookURL("http://myapp.com/webhook/")
|
||||||
resp, _ := c.Post(req)
|
resp, _ := c.Post(req)
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### PHP
|
### PHP
|
||||||
@@ -42,7 +41,7 @@ use TheCodingMachine\Gotenberg\DocumentFactory;
|
|||||||
use TheCodingMachine\Gotenberg\HTMLRequest;
|
use TheCodingMachine\Gotenberg\HTMLRequest;
|
||||||
|
|
||||||
$client = new Client('http://localhost:3000', new \Http\Adapter\Guzzle6\Client());
|
$client = new Client('http://localhost:3000', new \Http\Adapter\Guzzle6\Client());
|
||||||
$index = DocumentFactory::makeFromPath('index.html', 'index.html');
|
$index = DocumentFactory::makeFromPath('index.html', '/path/to/file');
|
||||||
$request = new HTMLRequest($index);
|
$request = new HTMLRequest($index);
|
||||||
$request->setWebhookURL('http://myapp.com/webhook/');
|
$request->setWebhookURL('http://myapp.com/webhook/');
|
||||||
$resp = $client->post($request);
|
$resp = $client->post($request);
|
||||||
@@ -58,9 +57,7 @@ It takes a float as value (e.g `2.5` for 2.5 seconds).
|
|||||||
|
|
||||||
> You may also define this value globally: see the [environment variables](#environment_variables.default_webhook_url_timeout) section.
|
> You may also define this value globally: see the [environment variables](#environment_variables.default_webhook_url_timeout) section.
|
||||||
|
|
||||||
### Examples
|
### cURL
|
||||||
|
|
||||||
#### cURL
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ curl --request POST \
|
$ curl --request POST \
|
||||||
@@ -71,21 +68,20 @@ $ curl --request POST \
|
|||||||
--form webhookURLTimeout=2.5
|
--form webhookURLTimeout=2.5
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Go
|
### Go
|
||||||
|
|
||||||
```golang
|
```golang
|
||||||
import "github.com/thecodingmachine/gotenberg-go-client/v6"
|
import "github.com/thecodingmachine/gotenberg-go-client/v7"
|
||||||
|
|
||||||
func main() {
|
c := &gotenberg.Client{Hostname: "http://localhost:3000"}
|
||||||
c := &gotenberg.Client{Hostname: "http://localhost:3000"}
|
index, _ := gotenberg.NewDocumentFromPath("index.html", "/path/to/file")
|
||||||
req, _ := gotenberg.NewHTMLRequest("index.html")
|
req := gotenberg.NewHTMLRequest(index)
|
||||||
req.WebhookURL("http://myapp.com/webhook/")
|
req.WebhookURL("http://myapp.com/webhook/")
|
||||||
req.WebhookURLTimeout(2.5)
|
req.WebhookURLTimeout(2.5)
|
||||||
resp, _ := c.Post(req)
|
resp, _ := c.Post(req)
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### PHP
|
### PHP
|
||||||
|
|
||||||
```php
|
```php
|
||||||
use TheCodingMachine\Gotenberg\Client;
|
use TheCodingMachine\Gotenberg\Client;
|
||||||
@@ -93,9 +89,61 @@ use TheCodingMachine\Gotenberg\DocumentFactory;
|
|||||||
use TheCodingMachine\Gotenberg\HTMLRequest;
|
use TheCodingMachine\Gotenberg\HTMLRequest;
|
||||||
|
|
||||||
$client = new Client('http://localhost:3000', new \Http\Adapter\Guzzle6\Client());
|
$client = new Client('http://localhost:3000', new \Http\Adapter\Guzzle6\Client());
|
||||||
$index = DocumentFactory::makeFromPath('index.html', 'index.html');
|
$index = DocumentFactory::makeFromPath('index.html', '/path/to/file');
|
||||||
$request = new HTMLRequest($index);
|
$request = new HTMLRequest($index);
|
||||||
$request->setWebhookURL('http://myapp.com/webhook/');
|
$request->setWebhookURL('http://myapp.com/webhook/');
|
||||||
$request->setWebhookURLTimeout(2.5);
|
$request->setWebhookURLTimeout(2.5);
|
||||||
$resp = $client->post($request);
|
$resp = $client->post($request);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Custom HTTP headers
|
||||||
|
|
||||||
|
You may send your own HTTP headers to the `webhookURL`.
|
||||||
|
|
||||||
|
For instance, by adding the HTTP header `Gotenberg-Webhookurl-Your-Header` to your request,
|
||||||
|
the API will send a request to the `webhookURL` with the HTTP header `Your-Header`.
|
||||||
|
|
||||||
|
> **Attention:** the API uses a canonical format for the HTTP headers:
|
||||||
|
> it transforms the first
|
||||||
|
> letter and any letter following a hyphen to upper case;
|
||||||
|
> the rest are converted to lowercase. For example, the
|
||||||
|
> canonical key for `accept-encoding` is `Accept-Encoding`.
|
||||||
|
|
||||||
|
### cURL
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ curl --request POST \
|
||||||
|
--url http://localhost:3000/convert/html \
|
||||||
|
--header 'Content-Type: multipart/form-data' \
|
||||||
|
--header 'Gotenberg-Webhookurl-Your-Header: Foo' \
|
||||||
|
--form files=@index.html \
|
||||||
|
--form webhookURL='http://myapp.com/webhook/'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Go
|
||||||
|
|
||||||
|
```golang
|
||||||
|
import "github.com/thecodingmachine/gotenberg-go-client/v7"
|
||||||
|
|
||||||
|
c := &gotenberg.Client{Hostname: "http://localhost:3000"}
|
||||||
|
index, _ := gotenberg.NewDocumentFromPath("index.html", "/path/to/file")
|
||||||
|
req := gotenberg.NewHTMLRequest(index)
|
||||||
|
req.WebhookURL("http://myapp.com/webhook/")
|
||||||
|
req.AddWebhookURLHTTPHeader("Your-Header", "Foo")
|
||||||
|
resp, _ := c.Post(req)
|
||||||
|
```
|
||||||
|
|
||||||
|
### PHP
|
||||||
|
|
||||||
|
```php
|
||||||
|
use TheCodingMachine\Gotenberg\Client;
|
||||||
|
use TheCodingMachine\Gotenberg\DocumentFactory;
|
||||||
|
use TheCodingMachine\Gotenberg\HTMLRequest;
|
||||||
|
|
||||||
|
$client = new Client('http://localhost:3000', new \Http\Adapter\Guzzle6\Client());
|
||||||
|
$index = DocumentFactory::makeFromPath('index.html', '/path/to/file');
|
||||||
|
$request = new HTMLRequest($index);
|
||||||
|
$request->setWebhookURL('http://myapp.com/webhook/');
|
||||||
|
$request->addWebhookURLHTTPHeader('Your-Header', 'Foo');
|
||||||
|
$resp = $client->post($request);
|
||||||
|
```
|
||||||
@@ -24,14 +24,13 @@ $ curl --request POST \
|
|||||||
### Go
|
### Go
|
||||||
|
|
||||||
```golang
|
```golang
|
||||||
import "github.com/thecodingmachine/gotenberg-go-client/v6"
|
import "github.com/thecodingmachine/gotenberg-go-client/v7"
|
||||||
|
|
||||||
func main() {
|
c := &gotenberg.Client{Hostname: "http://localhost:3000"}
|
||||||
c := &gotenberg.Client{Hostname: "http://localhost:3000"}
|
index, _ := gotenberg.NewDocumentFromPath("index.html", "/path/to/file")
|
||||||
req, _ := gotenberg.NewHTMLRequest("index.html")
|
req := gotenberg.NewHTMLRequest(index)
|
||||||
req.ResultFilename("foo.pdf")
|
req.ResultFilename("foo.pdf")
|
||||||
resp, _ := c.Post(req)
|
resp, _ := c.Post(req)
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### PHP
|
### PHP
|
||||||
@@ -43,7 +42,7 @@ use TheCodingMachine\Gotenberg\HTMLRequest;
|
|||||||
use TheCodingMachine\Gotenberg\Request;
|
use TheCodingMachine\Gotenberg\Request;
|
||||||
|
|
||||||
$client = new Client('http://localhost:3000', new \Http\Adapter\Guzzle6\Client());
|
$client = new Client('http://localhost:3000', new \Http\Adapter\Guzzle6\Client());
|
||||||
$index = DocumentFactory::makeFromPath('index.html', 'index.html');
|
$index = DocumentFactory::makeFromPath('index.html', '/path/to/file');
|
||||||
$request = new HTMLRequest($index);
|
$request = new HTMLRequest($index);
|
||||||
$request->setResultFilename('foo.pdf');
|
$request->setResultFilename('foo.pdf');
|
||||||
$resp = $client->post($request);
|
$resp = $client->post($request);
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ title: Scalability
|
|||||||
The API uses under the hood intricate programs.
|
The API uses under the hood intricate programs.
|
||||||
|
|
||||||
Gotenberg tries to abstract as much complexity as possible but it can
|
Gotenberg tries to abstract as much complexity as possible but it can
|
||||||
only do it to a certain extend.
|
only do it to a certain extent.
|
||||||
|
|
||||||
For instance, [Office](#office) and [Merge](#merge) endpoints will start respectively as many LibreOffice (unoconv) and PDTk
|
For instance, [Office](#office) and [Merge](#merge) endpoints will start respectively as many LibreOffice (unoconv) and PDTk
|
||||||
instances are there are requests. The limitation here is the available memory and CPU usage.
|
instances as there are requests. The limitation here is the available memory and CPU usage.
|
||||||
|
|
||||||
On another hand, for the [HTML](#html), [URL](#url) and [Markdown](#markdown) endpoints, the API does only 6 conversions in parallel.
|
On another hand, for the [HTML](#html), [URL](#url) and [Markdown](#markdown) endpoints, the API does only 6 conversions in parallel.
|
||||||
Indeed, Google Chrome misbehaves if there are too many concurrent conversions.
|
Indeed, Google Chrome misbehaves if there are too many concurrent conversions.
|
||||||
|
|||||||
@@ -2,8 +2,10 @@
|
|||||||
title: Links
|
title: Links
|
||||||
---
|
---
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="https://user-images.githubusercontent.com/8983173/50009948-84b01e00-ffb8-11e8-850b-fc240382c626.png" alt="Gotenberg logo" width="150" height="150" />
|
||||||
|
</p>
|
||||||
|
|
||||||
* Follow the progress on the [GitHub repository](https://github.com/thecodingmachine/gotenberg)
|
* Follow the progress on the [GitHub repository](https://github.com/thecodingmachine/gotenberg)
|
||||||
* Follow [@gulnap](https://twitter.com/gulnap) on Twitter
|
* 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 its help and its wonderful [cdp](https://github.com/mafredri/cdp) library
|
||||||
|
|
||||||
Psst: TheCodingMachine is always looking for [talented coders](https://coders.thecodingmachine.com).
|
|
||||||
|
|||||||
@@ -33,4 +33,4 @@ RUN go mod download &&\
|
|||||||
# Copy our code source.
|
# Copy our code source.
|
||||||
COPY --chown=gotenberg:gotenberg . .
|
COPY --chown=gotenberg:gotenberg . .
|
||||||
|
|
||||||
CMD ["golangci-lint", "run" ,"--tests=false", "--enable-all", "--disable=dupl", "--disable=funlen" ]
|
CMD ["golangci-lint", "run" ,"--tests=false", "--enable-all", "--disable=dupl", "--disable=funlen", "--disable=wsl", "--disable=gocognit" ]
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
# | Binary
|
# | Binary
|
||||||
# |--------------------------------------------------------------------------
|
# |--------------------------------------------------------------------------
|
||||||
# |
|
# |
|
||||||
# | Buils Gotenberg binary.
|
# | Builds Gotenberg binary.
|
||||||
# |
|
# |
|
||||||
|
|
||||||
FROM thecodingmachine/gotenberg:workspace AS workspace
|
FROM thecodingmachine/gotenberg:workspace AS workspace
|
||||||
@@ -42,7 +42,7 @@ LABEL authors="Julien Neuhart <j.neuhart@thecodingmachine.com>"
|
|||||||
|
|
||||||
ARG TINI_VERSION
|
ARG TINI_VERSION
|
||||||
|
|
||||||
ADD https://github.com/krallin/tini/releases/download/v${TINI_VERSION}/tini /tini
|
ADD --chown=gotenberg https://github.com/krallin/tini/releases/download/v${TINI_VERSION}/tini-static /tini
|
||||||
RUN chmod +x /tini
|
RUN chmod +x /tini
|
||||||
ENTRYPOINT [ "/tini", "--" ]
|
ENTRYPOINT [ "/tini", "--" ]
|
||||||
|
|
||||||
@@ -59,4 +59,4 @@ USER gotenberg
|
|||||||
WORKDIR /gotenberg
|
WORKDIR /gotenberg
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
CMD [ "gotenberg" ]
|
CMD [ "gotenberg" ]
|
||||||
|
|||||||
663
docs/index.html
663
docs/index.html
File diff suppressed because it is too large
Load Diff
5
docs/theme/gotenberg/views/index.html
vendored
5
docs/theme/gotenberg/views/index.html
vendored
@@ -34,7 +34,7 @@
|
|||||||
<div class="Container">
|
<div class="Container">
|
||||||
<div class="Header">
|
<div class="Header">
|
||||||
<div class="Title center">
|
<div class="Title center">
|
||||||
<img src="https://user-images.githubusercontent.com/8983173/50009948-84b01e00-ffb8-11e8-850b-fc240382c626.png" alt="Gotenberg logo" width="250" height="250" />
|
<img src="https://user-images.githubusercontent.com/8983173/69229423-ac731300-0b85-11ea-8c2e-2cc00ecdb269.PNG" alt="Gotenberg logo" width="250" height="250" />
|
||||||
<span class="text">{{.Title}}</span>
|
<span class="text">{{.Title}}</span>
|
||||||
<span class="subtext">{{.Subtitle}}</span>
|
<span class="subtext">{{.Subtitle}}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -58,7 +58,8 @@
|
|||||||
{{.Content}}
|
{{.Content}}
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
<div class="Footer"></div>
|
<div class="Footer">
|
||||||
|
</div>
|
||||||
<script src="theme/gotenberg/js/index.js"></script>
|
<script src="theme/gotenberg/js/index.js"></script>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
17
go.mod
17
go.mod
@@ -8,20 +8,23 @@ require (
|
|||||||
github.com/gorilla/websocket v1.4.1 // indirect
|
github.com/gorilla/websocket v1.4.1 // indirect
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
|
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
|
||||||
github.com/kr/pretty v0.1.0 // indirect
|
github.com/kr/pretty v0.1.0 // indirect
|
||||||
github.com/labstack/echo/v4 v4.1.10
|
github.com/labstack/echo/v4 v4.1.11
|
||||||
github.com/labstack/gommon v0.3.0
|
github.com/labstack/gommon v0.3.0
|
||||||
github.com/mafredri/cdp v0.24.2
|
github.com/mafredri/cdp v0.25.0
|
||||||
github.com/mattn/go-isatty v0.0.9
|
github.com/mattn/go-colorable v0.1.4 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.11
|
||||||
github.com/microcosm-cc/bluemonday v1.0.2
|
github.com/microcosm-cc/bluemonday v1.0.2
|
||||||
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2
|
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2
|
||||||
github.com/russross/blackfriday/v2 v2.0.1
|
github.com/russross/blackfriday/v2 v2.0.1
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
||||||
github.com/sirupsen/logrus v1.4.2
|
github.com/sirupsen/logrus v1.4.2
|
||||||
github.com/stretchr/testify v1.4.0
|
github.com/stretchr/testify v1.4.0
|
||||||
golang.org/x/crypto v0.0.0-20190927123631-a832865fa7ad // indirect
|
github.com/valyala/fasttemplate v1.1.0 // indirect
|
||||||
golang.org/x/net v0.0.0-20190930134127-c5a3c61f89f3 // 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/sync v0.0.0-20190911185100-cd5d95a43a6e
|
||||||
golang.org/x/sys v0.0.0-20190927073244-c990c680b611 // indirect
|
golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 // indirect
|
||||||
golang.org/x/text v0.3.2
|
golang.org/x/text v0.3.2
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.2.7 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
33
go.sum
33
go.sum
@@ -18,18 +18,22 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
|
|||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
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 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/labstack/echo/v4 v4.1.10 h1:/yhIpO50CBInUbE/nHJtGIyhBv0dJe2cDAYxc3V3uMo=
|
github.com/labstack/echo/v4 v4.1.11 h1:z0BZoArY4FqdpUEl+wlHp4hnr/oSR6MTmQmv8OHSoww=
|
||||||
github.com/labstack/echo/v4 v4.1.10/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
|
github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
|
||||||
github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0=
|
github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0=
|
||||||
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
|
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
|
||||||
github.com/mafredri/cdp v0.24.2 h1:Rzhj/EQw9opbiwUpNML7P+4Hvf0/nSYPaDbiCEpILOM=
|
github.com/mafredri/cdp v0.25.0 h1:nXhdpjP8Nrhw+9jnMlljEF6xs2B3vXI/wSiKDOvOXGs=
|
||||||
github.com/mafredri/cdp v0.24.2/go.mod h1:hgdiA0yp1uqhSaDOHJWPgXpMbh+LAfUdD9vbN2AM8gE=
|
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/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
|
||||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
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-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
|
||||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
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 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.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/microcosm-cc/bluemonday v1.0.2 h1:5lPfLTTAvAbtS0VqT+94yOtFnGfUWYyx0+iToC3Os3s=
|
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/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
|
||||||
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc=
|
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc=
|
||||||
@@ -51,15 +55,17 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw
|
|||||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
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 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8=
|
||||||
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
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=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
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 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
|
||||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190927123631-a832865fa7ad h1:5E5raQxcv+6CZ11RrBYQe5WRbUIWpScjh0kvHZkZIrQ=
|
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g=
|
||||||
golang.org/x/crypto v0.0.0-20190927123631-a832865fa7ad/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190930134127-c5a3c61f89f3 h1:6KET3Sqa7fkVfD63QnAM81ZeYg5n4HwApOJkufONnHA=
|
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8=
|
||||||
golang.org/x/net v0.0.0-20190930134127-c5a3c61f89f3/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
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/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@@ -68,14 +74,17 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/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 h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
|
||||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190927073244-c990c680b611 h1:q9u40nxWT5zRClI/uU9dHCiYGottAg6Nzz4YUQyHxdA=
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190927073244-c990c680b611/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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
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 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
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-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
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/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
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=
|
||||||
|
|||||||
@@ -15,31 +15,45 @@ import (
|
|||||||
"github.com/thecodingmachine/gotenberg/internal/pkg/xtime"
|
"github.com/thecodingmachine/gotenberg/internal/pkg/xtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
func pingEndpoint(config conf.Config) string {
|
||||||
pingEndpoint string = "/ping"
|
return fmt.Sprintf("%s%s", config.RootPath(), "ping")
|
||||||
mergeEndpoint string = "/merge"
|
}
|
||||||
convertGroupEndpoint string = "/convert"
|
|
||||||
htmlEndpoint string = "/html"
|
func mergeEndpoint(config conf.Config) string {
|
||||||
urlEndpoint string = "/url"
|
return fmt.Sprintf("%s%s", config.RootPath(), "merge")
|
||||||
markdownEndpoint string = "/markdown"
|
}
|
||||||
officeEndpoint string = "/office"
|
|
||||||
)
|
func htmlEndpoint(config conf.Config) string {
|
||||||
|
return fmt.Sprintf("%s%s", config.RootPath(), "convert/html")
|
||||||
|
}
|
||||||
|
|
||||||
|
func urlEndpoint(config conf.Config) string {
|
||||||
|
return fmt.Sprintf("%s%s", config.RootPath(), "convert/url")
|
||||||
|
}
|
||||||
|
|
||||||
|
func markdownEndpoint(config conf.Config) string {
|
||||||
|
return fmt.Sprintf("%s%s", config.RootPath(), "convert/markdown")
|
||||||
|
}
|
||||||
|
|
||||||
|
func officeEndpoint(config conf.Config) string {
|
||||||
|
return fmt.Sprintf("%s%s", config.RootPath(), "convert/office")
|
||||||
|
}
|
||||||
|
|
||||||
func isMultipartFormDataEndpoint(config conf.Config, path string) bool {
|
func isMultipartFormDataEndpoint(config conf.Config, path string) bool {
|
||||||
var multipartFormDataEndpoints []string
|
var multipartFormDataEndpoints []string
|
||||||
multipartFormDataEndpoints = append(multipartFormDataEndpoints, mergeEndpoint)
|
multipartFormDataEndpoints = append(multipartFormDataEndpoints, mergeEndpoint(config))
|
||||||
if !config.DisableGoogleChrome() {
|
if !config.DisableGoogleChrome() {
|
||||||
multipartFormDataEndpoints = append(
|
multipartFormDataEndpoints = append(
|
||||||
multipartFormDataEndpoints,
|
multipartFormDataEndpoints,
|
||||||
fmt.Sprintf("%s%s", convertGroupEndpoint, htmlEndpoint),
|
htmlEndpoint(config),
|
||||||
fmt.Sprintf("%s%s", convertGroupEndpoint, urlEndpoint),
|
urlEndpoint(config),
|
||||||
fmt.Sprintf("%s%s", convertGroupEndpoint, markdownEndpoint),
|
markdownEndpoint(config),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if !config.DisableUnoconv() {
|
if !config.DisableUnoconv() {
|
||||||
multipartFormDataEndpoints = append(
|
multipartFormDataEndpoints = append(
|
||||||
multipartFormDataEndpoints,
|
multipartFormDataEndpoints,
|
||||||
fmt.Sprintf("%s%s", convertGroupEndpoint, officeEndpoint),
|
officeEndpoint(config),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
for _, endpoint := range multipartFormDataEndpoints {
|
for _, endpoint := range multipartFormDataEndpoints {
|
||||||
@@ -124,6 +138,7 @@ func urlHandler(c echo.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
opts.CustomHTTPHeaders = resource.RemoteURLCustomHTTPHeaders(r)
|
||||||
if !r.HasArg(resource.RemoteURLArgKey) {
|
if !r.HasArg(resource.RemoteURLArgKey) {
|
||||||
return xerror.Invalid(
|
return xerror.Invalid(
|
||||||
op,
|
op,
|
||||||
@@ -308,20 +323,50 @@ func convertAsync(ctx context.Context, p printer.Printer, filename, fpath string
|
|||||||
defer f.Close() // nolint: errcheck
|
defer f.Close() // nolint: errcheck
|
||||||
logger.DebugfOp(
|
logger.DebugfOp(
|
||||||
op,
|
op,
|
||||||
"sending result file '%s' to '%s'",
|
"preparing to send result file '%s' to '%s'...",
|
||||||
filename,
|
filename,
|
||||||
webhookURL,
|
webhookURL,
|
||||||
)
|
)
|
||||||
httpClient := &http.Client{
|
httpClient := &http.Client{
|
||||||
Timeout: xtime.Duration(webhookURLTimeout),
|
Timeout: xtime.Duration(webhookURLTimeout),
|
||||||
}
|
}
|
||||||
resp, err := httpClient.Post(webhookURL, "application/pdf", f) /* #nosec */
|
req, err := http.NewRequest(http.MethodPost, webhookURL, f)
|
||||||
|
if err != nil {
|
||||||
|
xerr := xerror.New(op, err)
|
||||||
|
logger.ErrorOp(xerror.Op(xerr), xerr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req.Header.Set(echo.HeaderContentType, "application/pdf")
|
||||||
|
// set custom headers (if any).
|
||||||
|
customHTTPHeaders := resource.WebhookURLCustomHTTPHeaders(r)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.DebugOp(op, "skipping custom HTTP headers as none have been provided...")
|
||||||
|
}
|
||||||
|
// send the result file.
|
||||||
|
logger.DebugfOp(
|
||||||
|
op,
|
||||||
|
"sending result file '%s' to '%s'...",
|
||||||
|
filename,
|
||||||
|
webhookURL,
|
||||||
|
)
|
||||||
|
resp, err := httpClient.Do(req) /* #nosec */
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xerr := xerror.New(op, err)
|
xerr := xerror.New(op, err)
|
||||||
logger.ErrorOp(xerror.Op(xerr), xerr)
|
logger.ErrorOp(xerror.Op(xerr), xerr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer resp.Body.Close() // nolint: errcheck
|
defer resp.Body.Close() // nolint: errcheck
|
||||||
|
logger.DebugfOp(
|
||||||
|
op,
|
||||||
|
"result file '%s' sent to '%s'",
|
||||||
|
filename,
|
||||||
|
webhookURL,
|
||||||
|
)
|
||||||
}()
|
}()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,49 +19,51 @@ func TestPingHandler(t *testing.T) {
|
|||||||
// should return 200.
|
// should return 200.
|
||||||
config := conf.DefaultConfig()
|
config := conf.DefaultConfig()
|
||||||
srv := New(config)
|
srv := New(config)
|
||||||
req := httptest.NewRequest(http.MethodGet, pingEndpoint, nil)
|
endpoint := pingEndpoint(config)
|
||||||
|
req := httptest.NewRequest(http.MethodGet, endpoint, nil)
|
||||||
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
||||||
// should return 405 as Method is wrong.
|
// should return 405 as Method is wrong.
|
||||||
req = httptest.NewRequest(http.MethodPost, pingEndpoint, nil)
|
req = httptest.NewRequest(http.MethodPost, endpoint, nil)
|
||||||
test.AssertStatusCode(t, http.StatusMethodNotAllowed, srv, req)
|
test.AssertStatusCode(t, http.StatusMethodNotAllowed, srv, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMergeHandler(t *testing.T) {
|
func TestMergeHandler(t *testing.T) {
|
||||||
config := conf.DefaultConfig()
|
config := conf.DefaultConfig()
|
||||||
srv := New(config)
|
srv := New(config)
|
||||||
|
endpoint := mergeEndpoint(config)
|
||||||
// should return 200.
|
// should return 200.
|
||||||
body, contentType := test.MergeMultipartForm(t, nil)
|
body, contentType := test.MergeMultipartForm(t, nil)
|
||||||
req := httptest.NewRequest(http.MethodPost, mergeEndpoint, body)
|
req := httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||||
req.Header.Set(echo.HeaderContentType, contentType)
|
req.Header.Set(echo.HeaderContentType, contentType)
|
||||||
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
||||||
// should return 405 as Method is wrong.
|
// should return 405 as Method is wrong.
|
||||||
req = httptest.NewRequest(http.MethodGet, mergeEndpoint, nil)
|
req = httptest.NewRequest(http.MethodGet, endpoint, nil)
|
||||||
test.AssertStatusCode(t, http.StatusMethodNotAllowed, srv, req)
|
test.AssertStatusCode(t, http.StatusMethodNotAllowed, srv, req)
|
||||||
// should return 415 as Content-Type is wrong.
|
// should return 415 as Content-Type is wrong.
|
||||||
body, _ = test.MergeMultipartForm(t, nil)
|
body, _ = test.MergeMultipartForm(t, nil)
|
||||||
req = httptest.NewRequest(http.MethodPost, mergeEndpoint, body)
|
req = httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||||
test.AssertStatusCode(t, http.StatusUnsupportedMediaType, srv, req)
|
test.AssertStatusCode(t, http.StatusUnsupportedMediaType, srv, req)
|
||||||
// should return 400 as "waitTimeout" form field
|
// should return 400 as "waitTimeout" form field
|
||||||
// value is < 0.
|
// value is < 0.
|
||||||
body, contentType = test.MergeMultipartForm(t, map[string]string{string(resource.WaitTimeoutArgKey): "-1"})
|
body, contentType = test.MergeMultipartForm(t, map[string]string{string(resource.WaitTimeoutArgKey): "-1"})
|
||||||
req = httptest.NewRequest(http.MethodPost, mergeEndpoint, body)
|
req = httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||||
req.Header.Set(echo.HeaderContentType, contentType)
|
req.Header.Set(echo.HeaderContentType, contentType)
|
||||||
test.AssertStatusCode(t, http.StatusBadRequest, srv, req)
|
test.AssertStatusCode(t, http.StatusBadRequest, srv, req)
|
||||||
// should return 400 as "waitTimeout" form field
|
// should return 400 as "waitTimeout" form field
|
||||||
// value is is > config.MaximumWaitTimeout().
|
// value is is > config.MaximumWaitTimeout().
|
||||||
body, contentType = test.MergeMultipartForm(t, map[string]string{string(resource.WaitTimeoutArgKey): "31"})
|
body, contentType = test.MergeMultipartForm(t, map[string]string{string(resource.WaitTimeoutArgKey): "31"})
|
||||||
req = httptest.NewRequest(http.MethodPost, mergeEndpoint, body)
|
req = httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||||
req.Header.Set(echo.HeaderContentType, contentType)
|
req.Header.Set(echo.HeaderContentType, contentType)
|
||||||
test.AssertStatusCode(t, http.StatusBadRequest, srv, req)
|
test.AssertStatusCode(t, http.StatusBadRequest, srv, req)
|
||||||
// should return 400 as "waitTimeout" form field
|
// should return 400 as "waitTimeout" form field
|
||||||
// value is invalid.
|
// value is invalid.
|
||||||
body, contentType = test.MergeMultipartForm(t, map[string]string{string(resource.WaitTimeoutArgKey): "not a float"})
|
body, contentType = test.MergeMultipartForm(t, map[string]string{string(resource.WaitTimeoutArgKey): "not a float"})
|
||||||
req = httptest.NewRequest(http.MethodPost, mergeEndpoint, body)
|
req = httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||||
req.Header.Set(echo.HeaderContentType, contentType)
|
req.Header.Set(echo.HeaderContentType, contentType)
|
||||||
test.AssertStatusCode(t, http.StatusBadRequest, srv, req)
|
test.AssertStatusCode(t, http.StatusBadRequest, srv, req)
|
||||||
// should return 504.
|
// should return 504.
|
||||||
body, contentType = test.MergeMultipartForm(t, map[string]string{string(resource.WaitTimeoutArgKey): "0"})
|
body, contentType = test.MergeMultipartForm(t, map[string]string{string(resource.WaitTimeoutArgKey): "0"})
|
||||||
req = httptest.NewRequest(http.MethodPost, mergeEndpoint, body)
|
req = httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||||
req.Header.Set(echo.HeaderContentType, contentType)
|
req.Header.Set(echo.HeaderContentType, contentType)
|
||||||
test.AssertStatusCode(t, http.StatusGatewayTimeout, srv, req)
|
test.AssertStatusCode(t, http.StatusGatewayTimeout, srv, req)
|
||||||
}
|
}
|
||||||
@@ -69,7 +71,7 @@ func TestMergeHandler(t *testing.T) {
|
|||||||
func TestHTMLHandler(t *testing.T) {
|
func TestHTMLHandler(t *testing.T) {
|
||||||
config := conf.DefaultConfig()
|
config := conf.DefaultConfig()
|
||||||
srv := New(config)
|
srv := New(config)
|
||||||
endpoint := fmt.Sprintf("%s%s", convertGroupEndpoint, htmlEndpoint)
|
endpoint := htmlEndpoint(config)
|
||||||
// should return 200.
|
// should return 200.
|
||||||
body, contentType := test.HTMLMultipartForm(t, nil)
|
body, contentType := test.HTMLMultipartForm(t, nil)
|
||||||
req := httptest.NewRequest(http.MethodPost, endpoint, body)
|
req := httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||||
@@ -224,7 +226,7 @@ func TestHTMLHandler(t *testing.T) {
|
|||||||
func TestURLHandler(t *testing.T) {
|
func TestURLHandler(t *testing.T) {
|
||||||
config := conf.DefaultConfig()
|
config := conf.DefaultConfig()
|
||||||
srv := New(config)
|
srv := New(config)
|
||||||
endpoint := fmt.Sprintf("%s%s", convertGroupEndpoint, urlEndpoint)
|
endpoint := urlEndpoint(config)
|
||||||
// should return 200.
|
// should return 200.
|
||||||
body, contentType := test.URLMultipartForm(t, nil)
|
body, contentType := test.URLMultipartForm(t, nil)
|
||||||
req := httptest.NewRequest(http.MethodPost, endpoint, body)
|
req := httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||||
@@ -379,7 +381,7 @@ func TestURLHandler(t *testing.T) {
|
|||||||
func TestMarkdownHandler(t *testing.T) {
|
func TestMarkdownHandler(t *testing.T) {
|
||||||
config := conf.DefaultConfig()
|
config := conf.DefaultConfig()
|
||||||
srv := New(config)
|
srv := New(config)
|
||||||
endpoint := fmt.Sprintf("%s%s", convertGroupEndpoint, markdownEndpoint)
|
endpoint := markdownEndpoint(config)
|
||||||
// should return 200.
|
// should return 200.
|
||||||
body, contentType := test.MarkdownMultipartForm(t, nil)
|
body, contentType := test.MarkdownMultipartForm(t, nil)
|
||||||
req := httptest.NewRequest(http.MethodPost, endpoint, body)
|
req := httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||||
@@ -534,9 +536,10 @@ func TestMarkdownHandler(t *testing.T) {
|
|||||||
func TestOfficeHandler(t *testing.T) {
|
func TestOfficeHandler(t *testing.T) {
|
||||||
config := conf.DefaultConfig()
|
config := conf.DefaultConfig()
|
||||||
srv := New(config)
|
srv := New(config)
|
||||||
endpoint := fmt.Sprintf("%s%s", convertGroupEndpoint, officeEndpoint)
|
endpoint := officeEndpoint(config)
|
||||||
// should return 200.
|
// should return 200.
|
||||||
body, contentType := test.OfficeMultipartForm(t, nil)
|
// note: increased the wait timeout as it happens to timeout from time to time.
|
||||||
|
body, contentType := test.OfficeMultipartForm(t, map[string]string{string(resource.WaitTimeoutArgKey): "30"})
|
||||||
req := httptest.NewRequest(http.MethodPost, endpoint, body)
|
req := httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||||
req.Header.Set(echo.HeaderContentType, contentType)
|
req.Header.Set(echo.HeaderContentType, contentType)
|
||||||
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
||||||
@@ -579,11 +582,18 @@ func TestOfficeHandler(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestWebhook(t *testing.T) {
|
func TestWebhook(t *testing.T) {
|
||||||
|
customHeaderRealKey := http.CanonicalHeaderKey("MyCustomHeader")
|
||||||
|
customHeaderKey := fmt.Sprintf("%s%s", resource.WebhookURLCustomHTTPHeaderCanonicalBaseKey, customHeaderRealKey)
|
||||||
|
customHeaderValue := "foo"
|
||||||
status := make(chan error, 2)
|
status := make(chan error, 2)
|
||||||
rcv := echo.New()
|
rcv := echo.New()
|
||||||
rcv.POST("/foo", func(c echo.Context) error {
|
rcv.POST("/foo", func(c echo.Context) error {
|
||||||
if c.Request().Header.Get("Content-type") != "application/pdf" {
|
if c.Request().Header.Get(echo.HeaderContentType) != "application/pdf" {
|
||||||
status <- fmt.Errorf("wrong Content-type: got %s want %s", c.Request().Header.Get("Content-type"), "application/pdf")
|
status <- fmt.Errorf("wrong Content-type: got '%s' want '%s'", c.Request().Header.Get(echo.HeaderContentType), "application/pdf")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if c.Request().Header.Get(customHeaderRealKey) != customHeaderValue {
|
||||||
|
status <- fmt.Errorf("wrong '%s': got '%s' want '%s'", customHeaderRealKey, c.Request().Header.Get(customHeaderRealKey), customHeaderValue)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
body, err := ioutil.ReadAll(c.Request().Body)
|
body, err := ioutil.ReadAll(c.Request().Body)
|
||||||
@@ -605,8 +615,9 @@ func TestWebhook(t *testing.T) {
|
|||||||
srv := New(config)
|
srv := New(config)
|
||||||
// our custom server should receive the PDF.
|
// our custom server should receive the PDF.
|
||||||
body, contentType := test.MergeMultipartForm(t, map[string]string{string(resource.WebhookURLArgKey): "http://localhost:3001/foo"})
|
body, contentType := test.MergeMultipartForm(t, map[string]string{string(resource.WebhookURLArgKey): "http://localhost:3001/foo"})
|
||||||
req := httptest.NewRequest(http.MethodPost, mergeEndpoint, body)
|
req := httptest.NewRequest(http.MethodPost, mergeEndpoint(config), body)
|
||||||
req.Header.Set(echo.HeaderContentType, contentType)
|
req.Header.Set(echo.HeaderContentType, contentType)
|
||||||
|
req.Header.Set(customHeaderKey, customHeaderValue)
|
||||||
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
||||||
err := <-status
|
err := <-status
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@@ -616,9 +627,9 @@ func TestResultFilename(t *testing.T) {
|
|||||||
config := conf.DefaultConfig()
|
config := conf.DefaultConfig()
|
||||||
srv := New(config)
|
srv := New(config)
|
||||||
body, contentType := test.MergeMultipartForm(t, map[string]string{string(resource.ResultFilenameArgKey): "foo.pdf"})
|
body, contentType := test.MergeMultipartForm(t, map[string]string{string(resource.ResultFilenameArgKey): "foo.pdf"})
|
||||||
req := httptest.NewRequest(http.MethodPost, mergeEndpoint, body)
|
req := httptest.NewRequest(http.MethodPost, mergeEndpoint(config), body)
|
||||||
req.Header.Set(echo.HeaderContentType, contentType)
|
req.Header.Set(echo.HeaderContentType, contentType)
|
||||||
rec := httptest.NewRecorder()
|
rec := httptest.NewRecorder()
|
||||||
srv.ServeHTTP(rec, req)
|
srv.ServeHTTP(rec, req)
|
||||||
assert.Equal(t, "attachment; filename=\"foo.pdf\"", rec.Header().Get("Content-Disposition"))
|
assert.Equal(t, "attachment; filename=\"foo.pdf\"", rec.Header().Get(echo.HeaderContentDisposition))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ func contextMiddleware(config conf.Config) echo.MiddlewareFunc {
|
|||||||
// there is no need to create a Resource.
|
// there is no need to create a Resource.
|
||||||
if !isMultipartFormDataEndpoint(config, ctx.Path()) {
|
if !isMultipartFormDataEndpoint(config, ctx.Path()) {
|
||||||
// validate method for healthcheck endpoint.
|
// validate method for healthcheck endpoint.
|
||||||
if ctx.Path() == pingEndpoint && ctx.Request().Method != http.MethodGet {
|
if ctx.Path() == pingEndpoint(config) && ctx.Request().Method != http.MethodGet {
|
||||||
err := doErr(ctx, echo.NewHTTPError(http.StatusMethodNotAllowed))
|
err := doErr(ctx, echo.NewHTTPError(http.StatusMethodNotAllowed))
|
||||||
return ctx.LogRequestResult(err, false)
|
return ctx.LogRequestResult(err, false)
|
||||||
}
|
}
|
||||||
@@ -60,14 +60,14 @@ func contextMiddleware(config conf.Config) echo.MiddlewareFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// loggerMiddleware logs the result of a request.
|
// loggerMiddleware logs the result of a request.
|
||||||
func loggerMiddleware() echo.MiddlewareFunc {
|
func loggerMiddleware(config conf.Config) echo.MiddlewareFunc {
|
||||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
return func(c echo.Context) error {
|
return func(c echo.Context) error {
|
||||||
ctx := context.MustCastFromEchoContext(c)
|
ctx := context.MustCastFromEchoContext(c)
|
||||||
err := next(ctx)
|
err := next(ctx)
|
||||||
// we do not want to log healthcheck requests if
|
// we do not want to log healthcheck requests if
|
||||||
// log level is not set to DEBUG.
|
// log level is not set to DEBUG.
|
||||||
isDebug := ctx.Path() == pingEndpoint
|
isDebug := ctx.Path() == pingEndpoint(config)
|
||||||
return ctx.LogRequestResult(err, isDebug)
|
return ctx.LogRequestResult(err, isDebug)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,23 +48,34 @@ func chromePrinterOptions(r resource.Resource, config conf.Config) (printer.Chro
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return printer.ChromePrinterOptions{}, err
|
return printer.ChromePrinterOptions{}, err
|
||||||
}
|
}
|
||||||
|
pageRanges, err := r.StringArg(resource.PageRangesArgKey, "")
|
||||||
|
if err != nil {
|
||||||
|
return printer.ChromePrinterOptions{}, err
|
||||||
|
}
|
||||||
googleChromeRpccBufferSize, err := resource.GoogleChromeRpccBufferSizeArg(r, config)
|
googleChromeRpccBufferSize, err := resource.GoogleChromeRpccBufferSizeArg(r, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return printer.ChromePrinterOptions{}, err
|
return printer.ChromePrinterOptions{}, err
|
||||||
}
|
}
|
||||||
|
scale, err := resource.ScaleArg(r, config)
|
||||||
|
if err != nil {
|
||||||
|
return printer.ChromePrinterOptions{}, err
|
||||||
|
}
|
||||||
return printer.ChromePrinterOptions{
|
return printer.ChromePrinterOptions{
|
||||||
WaitTimeout: waitTimeout,
|
WaitTimeout: waitTimeout,
|
||||||
WaitDelay: waitDelay,
|
WaitDelay: waitDelay,
|
||||||
HeaderHTML: headerHTML,
|
HeaderHTML: headerHTML,
|
||||||
FooterHTML: footerHTML,
|
FooterHTML: footerHTML,
|
||||||
PaperWidth: paperWidth,
|
PaperWidth: paperWidth,
|
||||||
PaperHeight: paperHeight,
|
PaperHeight: paperHeight,
|
||||||
MarginTop: marginTop,
|
MarginTop: marginTop,
|
||||||
MarginBottom: marginBottom,
|
MarginBottom: marginBottom,
|
||||||
MarginLeft: marginLeft,
|
MarginLeft: marginLeft,
|
||||||
MarginRight: marginRight,
|
MarginRight: marginRight,
|
||||||
Landscape: landscape,
|
Landscape: landscape,
|
||||||
RpccBufferSize: googleChromeRpccBufferSize,
|
PageRanges: pageRanges,
|
||||||
|
RpccBufferSize: googleChromeRpccBufferSize,
|
||||||
|
CustomHTTPHeaders: make(map[string]string),
|
||||||
|
Scale: scale,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
opts, err := resolver()
|
opts, err := resolver()
|
||||||
@@ -85,9 +96,14 @@ func officePrinterOptions(r resource.Resource, config conf.Config) (printer.Offi
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return printer.OfficePrinterOptions{}, err
|
return printer.OfficePrinterOptions{}, err
|
||||||
}
|
}
|
||||||
|
pageRanges, err := r.StringArg(resource.PageRangesArgKey, "")
|
||||||
|
if err != nil {
|
||||||
|
return printer.OfficePrinterOptions{}, err
|
||||||
|
}
|
||||||
return printer.OfficePrinterOptions{
|
return printer.OfficePrinterOptions{
|
||||||
WaitTimeout: waitTimeout,
|
WaitTimeout: waitTimeout,
|
||||||
Landscape: landscape,
|
Landscape: landscape,
|
||||||
|
PageRanges: pageRanges,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
opts, err := resolver()
|
opts, err := resolver()
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import (
|
|||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"github.com/thecodingmachine/gotenberg/internal/app/xhttp/pkg/resource"
|
"github.com/thecodingmachine/gotenberg/internal/app/xhttp/pkg/resource"
|
||||||
"github.com/thecodingmachine/gotenberg/internal/pkg/conf"
|
"github.com/thecodingmachine/gotenberg/internal/pkg/conf"
|
||||||
"github.com/thecodingmachine/gotenberg/internal/pkg/normalize"
|
|
||||||
"github.com/thecodingmachine/gotenberg/internal/pkg/xerror"
|
"github.com/thecodingmachine/gotenberg/internal/pkg/xerror"
|
||||||
"github.com/thecodingmachine/gotenberg/internal/pkg/xlog"
|
"github.com/thecodingmachine/gotenberg/internal/pkg/xlog"
|
||||||
)
|
)
|
||||||
@@ -79,6 +78,10 @@ func (ctx *Context) WithResource(directoryName string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return r, err
|
return r, err
|
||||||
}
|
}
|
||||||
|
// retrieve custom headers from request.
|
||||||
|
for key, value := range ctx.Request().Header {
|
||||||
|
r.WithCustomHTTPHeader(key, value[0])
|
||||||
|
}
|
||||||
// retrieve form values from request.
|
// retrieve form values from request.
|
||||||
for _, key := range resource.ArgKeys() {
|
for _, key := range resource.ArgKeys() {
|
||||||
r.WithArg(key, ctx.FormValue(string(key)))
|
r.WithArg(key, ctx.FormValue(string(key)))
|
||||||
@@ -103,11 +106,7 @@ func (ctx *Context) WithResource(directoryName string) error {
|
|||||||
return r, err
|
return r, err
|
||||||
}
|
}
|
||||||
defer in.Close() // nolint: errcheck
|
defer in.Close() // nolint: errcheck
|
||||||
filename, err := normalize.String(fh.Filename)
|
if err := r.WithFile(fh.Filename, in); err != nil {
|
||||||
if err != nil {
|
|
||||||
return r, err
|
|
||||||
}
|
|
||||||
if err := r.WithFile(filename, in); err != nil {
|
|
||||||
return r, err
|
return r, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,9 +51,15 @@ const (
|
|||||||
// LandscapeArgKey is the key
|
// LandscapeArgKey is the key
|
||||||
// of the argument "landscape".
|
// of the argument "landscape".
|
||||||
LandscapeArgKey ArgKey = "landscape"
|
LandscapeArgKey ArgKey = "landscape"
|
||||||
|
// PageRangesArgKey is the key
|
||||||
|
// of the argument "pageRanges".
|
||||||
|
PageRangesArgKey ArgKey = "pageRanges"
|
||||||
// GoogleChromeRpccBufferSizeArgKey is the key
|
// GoogleChromeRpccBufferSizeArgKey is the key
|
||||||
// of the argument "googleChromeRpccBufferSize".
|
// of the argument "googleChromeRpccBufferSize".
|
||||||
GoogleChromeRpccBufferSizeArgKey ArgKey = "googleChromeRpccBufferSize"
|
GoogleChromeRpccBufferSizeArgKey ArgKey = "googleChromeRpccBufferSize"
|
||||||
|
// ScaleArgKey is the key
|
||||||
|
// of the argument "scale".
|
||||||
|
ScaleArgKey ArgKey = "scale"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -76,7 +82,9 @@ func ArgKeys() []ArgKey {
|
|||||||
MarginLeftArgKey,
|
MarginLeftArgKey,
|
||||||
MarginRightArgKey,
|
MarginRightArgKey,
|
||||||
LandscapeArgKey,
|
LandscapeArgKey,
|
||||||
|
PageRangesArgKey,
|
||||||
GoogleChromeRpccBufferSizeArgKey,
|
GoogleChromeRpccBufferSizeArgKey,
|
||||||
|
ScaleArgKey,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,3 +298,22 @@ func GoogleChromeRpccBufferSizeArg(r Resource, config conf.Config) (int64, error
|
|||||||
}
|
}
|
||||||
return result, nil
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,7 +24,9 @@ func TestArgKeys(t *testing.T) {
|
|||||||
MarginLeftArgKey,
|
MarginLeftArgKey,
|
||||||
MarginRightArgKey,
|
MarginRightArgKey,
|
||||||
LandscapeArgKey,
|
LandscapeArgKey,
|
||||||
|
PageRangesArgKey,
|
||||||
GoogleChromeRpccBufferSizeArgKey,
|
GoogleChromeRpccBufferSizeArgKey,
|
||||||
|
ScaleArgKey,
|
||||||
}
|
}
|
||||||
assert.Equal(t, expected, ArgKeys())
|
assert.Equal(t, expected, ArgKeys())
|
||||||
}
|
}
|
||||||
@@ -345,3 +347,50 @@ func TestGoogleChromeRpccBufferSizeArg(t *testing.T) {
|
|||||||
err = r.Close()
|
err = r.Close()
|
||||||
assert.Nil(t, err)
|
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)
|
||||||
|
}
|
||||||
|
|||||||
37
internal/app/xhttp/pkg/resource/header.go
Normal file
37
internal/app/xhttp/pkg/resource/header.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package resource
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// RemoteURLCustomHTTPHeaderCanonicalBaseKey is the base key
|
||||||
|
// of custom headers send to the remote URL.
|
||||||
|
RemoteURLCustomHTTPHeaderCanonicalBaseKey string = "Gotenberg-Remoteurl-"
|
||||||
|
// WebhookURLCustomHTTPHeaderCanonicalBaseKey is the base key
|
||||||
|
// of custom headers send to the webhook URL.
|
||||||
|
WebhookURLCustomHTTPHeaderCanonicalBaseKey string = "Gotenberg-Webhookurl-"
|
||||||
|
)
|
||||||
|
|
||||||
|
func fetchCustomHTTPHeaders(r Resource, baseKey string) map[string]string {
|
||||||
|
customHeaders := make(map[string]string)
|
||||||
|
for key, value := range r.customHeaders {
|
||||||
|
if strings.Contains(key, baseKey) {
|
||||||
|
realKey := strings.Replace(key, baseKey, "", 1)
|
||||||
|
customHeaders[realKey] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return customHeaders
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoteURLCustomHTTPHeaders is a helper for retrieving
|
||||||
|
// the custom headers for the URL conversion.
|
||||||
|
func RemoteURLCustomHTTPHeaders(r Resource) map[string]string {
|
||||||
|
return fetchCustomHTTPHeaders(r, RemoteURLCustomHTTPHeaderCanonicalBaseKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WebhookURLCustomHTTPHeaders is a helper for retrieving
|
||||||
|
// the custom headers for the webhook URL.
|
||||||
|
func WebhookURLCustomHTTPHeaders(r Resource) map[string]string {
|
||||||
|
return fetchCustomHTTPHeaders(r, WebhookURLCustomHTTPHeaderCanonicalBaseKey)
|
||||||
|
}
|
||||||
54
internal/app/xhttp/pkg/resource/header_test.go
Normal file
54
internal/app/xhttp/pkg/resource/header_test.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package resource
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/thecodingmachine/gotenberg/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRemoteURLCustomHeaders(t *testing.T) {
|
||||||
|
const resourceDirectoryName string = "foo"
|
||||||
|
logger := test.DebugLogger()
|
||||||
|
r, err := New(logger, resourceDirectoryName)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
// should find the custom header.
|
||||||
|
customHeaderValue := "bar"
|
||||||
|
customHeaderCanonicalRealKey := "Foo"
|
||||||
|
customHeaderCanonicalKey := http.CanonicalHeaderKey(fmt.Sprintf("%s%s", RemoteURLCustomHTTPHeaderCanonicalBaseKey, customHeaderCanonicalRealKey))
|
||||||
|
r.WithCustomHTTPHeader(customHeaderCanonicalKey, customHeaderValue)
|
||||||
|
r.WithCustomHTTPHeader("Bar", "Bar")
|
||||||
|
expected := map[string]string{
|
||||||
|
customHeaderCanonicalRealKey: customHeaderValue,
|
||||||
|
}
|
||||||
|
notExpected := map[string]string{
|
||||||
|
customHeaderCanonicalKey: customHeaderValue,
|
||||||
|
}
|
||||||
|
v := RemoteURLCustomHTTPHeaders(r)
|
||||||
|
assert.Equal(t, expected, v)
|
||||||
|
assert.NotEqual(t, notExpected, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWebhookURLCustomHeaders(t *testing.T) {
|
||||||
|
const resourceDirectoryName string = "foo"
|
||||||
|
logger := test.DebugLogger()
|
||||||
|
r, err := New(logger, resourceDirectoryName)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
// should find the custom header.
|
||||||
|
customHeaderValue := "bar"
|
||||||
|
customHeaderCanonicalRealKey := "Foo"
|
||||||
|
customHeaderCanonicalKey := http.CanonicalHeaderKey(fmt.Sprintf("%s%s", WebhookURLCustomHTTPHeaderCanonicalBaseKey, customHeaderCanonicalRealKey))
|
||||||
|
r.WithCustomHTTPHeader(customHeaderCanonicalKey, customHeaderValue)
|
||||||
|
r.WithCustomHTTPHeader("Bar", "Bar")
|
||||||
|
expected := map[string]string{
|
||||||
|
customHeaderCanonicalRealKey: customHeaderValue,
|
||||||
|
}
|
||||||
|
notExpected := map[string]string{
|
||||||
|
customHeaderCanonicalKey: customHeaderValue,
|
||||||
|
}
|
||||||
|
v := WebhookURLCustomHTTPHeaders(r)
|
||||||
|
assert.Equal(t, expected, v)
|
||||||
|
assert.NotEqual(t, notExpected, v)
|
||||||
|
}
|
||||||
@@ -3,9 +3,12 @@ package resource
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/thecodingmachine/gotenberg/internal/pkg/normalize"
|
||||||
"github.com/thecodingmachine/gotenberg/internal/pkg/xassert"
|
"github.com/thecodingmachine/gotenberg/internal/pkg/xassert"
|
||||||
"github.com/thecodingmachine/gotenberg/internal/pkg/xerror"
|
"github.com/thecodingmachine/gotenberg/internal/pkg/xerror"
|
||||||
"github.com/thecodingmachine/gotenberg/internal/pkg/xlog"
|
"github.com/thecodingmachine/gotenberg/internal/pkg/xlog"
|
||||||
@@ -21,10 +24,11 @@ const TemporaryDirectory string = "tmp"
|
|||||||
// Resource helps managing
|
// Resource helps managing
|
||||||
// arguments and files for a conversion.
|
// arguments and files for a conversion.
|
||||||
type Resource struct {
|
type Resource struct {
|
||||||
logger xlog.Logger
|
logger xlog.Logger
|
||||||
dirPath string
|
dirPath string
|
||||||
args map[ArgKey]string
|
customHeaders map[string]string
|
||||||
files map[string]file
|
args map[ArgKey]string
|
||||||
|
files map[string]file
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a Resource where its files will
|
// New creates a Resource where its files will
|
||||||
@@ -48,10 +52,11 @@ func New(logger xlog.Logger, directoryName string) (Resource, error) {
|
|||||||
}
|
}
|
||||||
logger.DebugfOp(op, "resource directory '%s' created", directoryName)
|
logger.DebugfOp(op, "resource directory '%s' created", directoryName)
|
||||||
return Resource{
|
return Resource{
|
||||||
logger: logger,
|
logger: logger,
|
||||||
dirPath: dirPath,
|
dirPath: dirPath,
|
||||||
args: make(map[ArgKey]string),
|
customHeaders: make(map[string]string),
|
||||||
files: make(map[string]file),
|
args: make(map[ArgKey]string),
|
||||||
|
files: make(map[string]file),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,6 +75,21 @@ func (r Resource) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithCustomHTTPHeader add a new custom header to the Resource.
|
||||||
|
// Given key should be in canonical format.
|
||||||
|
func (r *Resource) WithCustomHTTPHeader(key string, value string) {
|
||||||
|
const op string = "resource.Resource.WithCustomHTTPHeader"
|
||||||
|
// should already be in canonical format.
|
||||||
|
canonicalKey := http.CanonicalHeaderKey(key)
|
||||||
|
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)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.logger.DebugfOp(op, "skipping '%s' as it is not a custom HTTP header...", canonicalKey)
|
||||||
|
}
|
||||||
|
|
||||||
// WithArg add a new argument to the Resource.
|
// WithArg add a new argument to the Resource.
|
||||||
func (r *Resource) WithArg(key ArgKey, value string) {
|
func (r *Resource) WithArg(key ArgKey, value string) {
|
||||||
const op string = "resource.Resource.WithArg"
|
const op string = "resource.Resource.WithArg"
|
||||||
@@ -80,13 +100,24 @@ func (r *Resource) WithArg(key ArgKey, value string) {
|
|||||||
// WithFile add a new file to the Resource.
|
// WithFile add a new file to the Resource.
|
||||||
func (r *Resource) WithFile(filename string, in io.Reader) error {
|
func (r *Resource) WithFile(filename string, in io.Reader) error {
|
||||||
const op string = "resource.Resource.WithFile"
|
const op string = "resource.Resource.WithFile"
|
||||||
fpath := fmt.Sprintf("%s/%s", r.dirPath, filename)
|
resolver := func() error {
|
||||||
file := file{fpath: fpath}
|
// see https://github.com/thecodingmachine/gotenberg/issues/104.
|
||||||
if err := file.write(in); err != nil {
|
normalized, err := normalize.String(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fpath := fmt.Sprintf("%s/%s", r.dirPath, normalized)
|
||||||
|
file := file{fpath: fpath}
|
||||||
|
if err := file.write(in); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.files[filename] = file
|
||||||
|
r.logger.DebugfOp(op, "resource file '%s' created", filename)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := resolver(); err != nil {
|
||||||
return xerror.New(op, err)
|
return xerror.New(op, err)
|
||||||
}
|
}
|
||||||
r.files[filename] = file
|
|
||||||
r.logger.DebugfOp(op, "resource file '%s' created", filename)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,22 +11,21 @@ func New(config conf.Config) *echo.Echo {
|
|||||||
srv.HideBanner = true
|
srv.HideBanner = true
|
||||||
srv.HidePort = true
|
srv.HidePort = true
|
||||||
srv.Use(contextMiddleware(config))
|
srv.Use(contextMiddleware(config))
|
||||||
srv.Use(loggerMiddleware())
|
srv.Use(loggerMiddleware(config))
|
||||||
srv.Use(cleanupMiddleware())
|
srv.Use(cleanupMiddleware())
|
||||||
srv.Use(errorMiddleware())
|
srv.Use(errorMiddleware())
|
||||||
srv.GET(pingEndpoint, pingHandler)
|
srv.GET(pingEndpoint(config), pingHandler)
|
||||||
srv.POST(mergeEndpoint, mergeHandler)
|
srv.POST(mergeEndpoint(config), mergeHandler)
|
||||||
if config.DisableGoogleChrome() && config.DisableUnoconv() {
|
if config.DisableGoogleChrome() && config.DisableUnoconv() {
|
||||||
return srv
|
return srv
|
||||||
}
|
}
|
||||||
g := srv.Group(convertGroupEndpoint)
|
|
||||||
if !config.DisableGoogleChrome() {
|
if !config.DisableGoogleChrome() {
|
||||||
g.POST(htmlEndpoint, htmlHandler)
|
srv.POST(htmlEndpoint(config), htmlHandler)
|
||||||
g.POST(urlEndpoint, urlHandler)
|
srv.POST(urlEndpoint(config), urlHandler)
|
||||||
g.POST(markdownEndpoint, markdownHandler)
|
srv.POST(markdownEndpoint(config), markdownHandler)
|
||||||
}
|
}
|
||||||
if !config.DisableUnoconv() {
|
if !config.DisableUnoconv() {
|
||||||
g.POST(officeEndpoint, officeHandler)
|
srv.POST(officeEndpoint(config), officeHandler)
|
||||||
}
|
}
|
||||||
return srv
|
return srv
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package xhttp
|
package xhttp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"os"
|
"os"
|
||||||
@@ -28,31 +27,31 @@ func TestDisableChromeEndpoints(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
srv := New(config)
|
srv := New(config)
|
||||||
// Ping endpoint should return 200.
|
// Ping endpoint should return 200.
|
||||||
req := httptest.NewRequest(http.MethodGet, pingEndpoint, nil)
|
req := httptest.NewRequest(http.MethodGet, pingEndpoint(config), nil)
|
||||||
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
||||||
// Merge endpoint should return 200.
|
// Merge endpoint should return 200.
|
||||||
body, contentType := test.MergeMultipartForm(t, nil)
|
body, contentType := test.MergeMultipartForm(t, nil)
|
||||||
req = httptest.NewRequest(http.MethodPost, mergeEndpoint, body)
|
req = httptest.NewRequest(http.MethodPost, mergeEndpoint(config), body)
|
||||||
req.Header.Set(echo.HeaderContentType, contentType)
|
req.Header.Set(echo.HeaderContentType, contentType)
|
||||||
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
||||||
// HTML endpoint should return 404.
|
// HTML endpoint should return 404.
|
||||||
body, contentType = test.HTMLMultipartForm(t, nil)
|
body, contentType = test.HTMLMultipartForm(t, nil)
|
||||||
req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, htmlEndpoint), body)
|
req = httptest.NewRequest(http.MethodPost, htmlEndpoint(config), body)
|
||||||
req.Header.Set(echo.HeaderContentType, contentType)
|
req.Header.Set(echo.HeaderContentType, contentType)
|
||||||
test.AssertStatusCode(t, http.StatusNotFound, srv, req)
|
test.AssertStatusCode(t, http.StatusNotFound, srv, req)
|
||||||
// URL endpoint should return 404.
|
// URL endpoint should return 404.
|
||||||
body, contentType = test.URLMultipartForm(t, nil)
|
body, contentType = test.URLMultipartForm(t, nil)
|
||||||
req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, urlEndpoint), body)
|
req = httptest.NewRequest(http.MethodPost, urlEndpoint(config), body)
|
||||||
req.Header.Set(echo.HeaderContentType, contentType)
|
req.Header.Set(echo.HeaderContentType, contentType)
|
||||||
test.AssertStatusCode(t, http.StatusNotFound, srv, req)
|
test.AssertStatusCode(t, http.StatusNotFound, srv, req)
|
||||||
// Markdown endpoint should return 404.
|
// Markdown endpoint should return 404.
|
||||||
body, contentType = test.MarkdownMultipartForm(t, nil)
|
body, contentType = test.MarkdownMultipartForm(t, nil)
|
||||||
req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, markdownEndpoint), body)
|
req = httptest.NewRequest(http.MethodPost, markdownEndpoint(config), body)
|
||||||
req.Header.Set(echo.HeaderContentType, contentType)
|
req.Header.Set(echo.HeaderContentType, contentType)
|
||||||
test.AssertStatusCode(t, http.StatusNotFound, srv, req)
|
test.AssertStatusCode(t, http.StatusNotFound, srv, req)
|
||||||
// Office endpoint should return 200.
|
// Office endpoint should return 200.
|
||||||
body, contentType = test.OfficeMultipartForm(t, nil)
|
body, contentType = test.OfficeMultipartForm(t, nil)
|
||||||
req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, officeEndpoint), body)
|
req = httptest.NewRequest(http.MethodPost, officeEndpoint(config), body)
|
||||||
req.Header.Set(echo.HeaderContentType, contentType)
|
req.Header.Set(echo.HeaderContentType, contentType)
|
||||||
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
||||||
// finally...
|
// finally...
|
||||||
@@ -65,31 +64,31 @@ func TestDisableUnoconvEndpoints(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
srv := New(config)
|
srv := New(config)
|
||||||
// Ping endpoint should return 200.
|
// Ping endpoint should return 200.
|
||||||
req := httptest.NewRequest(http.MethodGet, pingEndpoint, nil)
|
req := httptest.NewRequest(http.MethodGet, pingEndpoint(config), nil)
|
||||||
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
||||||
// Merge endpoint should return 200.
|
// Merge endpoint should return 200.
|
||||||
body, contentType := test.MergeMultipartForm(t, nil)
|
body, contentType := test.MergeMultipartForm(t, nil)
|
||||||
req = httptest.NewRequest(http.MethodPost, mergeEndpoint, body)
|
req = httptest.NewRequest(http.MethodPost, mergeEndpoint(config), body)
|
||||||
req.Header.Set(echo.HeaderContentType, contentType)
|
req.Header.Set(echo.HeaderContentType, contentType)
|
||||||
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
||||||
// HTML endpoint should return 200.
|
// HTML endpoint should return 200.
|
||||||
body, contentType = test.HTMLMultipartForm(t, nil)
|
body, contentType = test.HTMLMultipartForm(t, nil)
|
||||||
req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, htmlEndpoint), body)
|
req = httptest.NewRequest(http.MethodPost, htmlEndpoint(config), body)
|
||||||
req.Header.Set(echo.HeaderContentType, contentType)
|
req.Header.Set(echo.HeaderContentType, contentType)
|
||||||
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
||||||
// URL endpoint should return 200.
|
// URL endpoint should return 200.
|
||||||
body, contentType = test.URLMultipartForm(t, nil)
|
body, contentType = test.URLMultipartForm(t, nil)
|
||||||
req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, urlEndpoint), body)
|
req = httptest.NewRequest(http.MethodPost, urlEndpoint(config), body)
|
||||||
req.Header.Set(echo.HeaderContentType, contentType)
|
req.Header.Set(echo.HeaderContentType, contentType)
|
||||||
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
||||||
// Markdown endpoint should return 200.
|
// Markdown endpoint should return 200.
|
||||||
body, contentType = test.MarkdownMultipartForm(t, nil)
|
body, contentType = test.MarkdownMultipartForm(t, nil)
|
||||||
req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, markdownEndpoint), body)
|
req = httptest.NewRequest(http.MethodPost, markdownEndpoint(config), body)
|
||||||
req.Header.Set(echo.HeaderContentType, contentType)
|
req.Header.Set(echo.HeaderContentType, contentType)
|
||||||
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
||||||
// Office endpoint should return 404.
|
// Office endpoint should return 404.
|
||||||
body, contentType = test.OfficeMultipartForm(t, nil)
|
body, contentType = test.OfficeMultipartForm(t, nil)
|
||||||
req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, officeEndpoint), body)
|
req = httptest.NewRequest(http.MethodPost, officeEndpoint(config), body)
|
||||||
req.Header.Set(echo.HeaderContentType, contentType)
|
req.Header.Set(echo.HeaderContentType, contentType)
|
||||||
test.AssertStatusCode(t, http.StatusNotFound, srv, req)
|
test.AssertStatusCode(t, http.StatusNotFound, srv, req)
|
||||||
// finally...
|
// finally...
|
||||||
@@ -102,34 +101,71 @@ func TestDisableChromeAndUnoconvEndpoints(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
srv := New(config)
|
srv := New(config)
|
||||||
// Ping endpoint should return 200.
|
// Ping endpoint should return 200.
|
||||||
req := httptest.NewRequest(http.MethodGet, pingEndpoint, nil)
|
req := httptest.NewRequest(http.MethodGet, pingEndpoint(config), nil)
|
||||||
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
||||||
// Merge endpoint should return 200.
|
// Merge endpoint should return 200.
|
||||||
body, contentType := test.MergeMultipartForm(t, nil)
|
body, contentType := test.MergeMultipartForm(t, nil)
|
||||||
req = httptest.NewRequest(http.MethodPost, mergeEndpoint, body)
|
req = httptest.NewRequest(http.MethodPost, mergeEndpoint(config), body)
|
||||||
req.Header.Set(echo.HeaderContentType, contentType)
|
req.Header.Set(echo.HeaderContentType, contentType)
|
||||||
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
||||||
// HTML endpoint should return 404.
|
// HTML endpoint should return 404.
|
||||||
body, contentType = test.HTMLMultipartForm(t, nil)
|
body, contentType = test.HTMLMultipartForm(t, nil)
|
||||||
req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, htmlEndpoint), body)
|
req = httptest.NewRequest(http.MethodPost, htmlEndpoint(config), body)
|
||||||
req.Header.Set(echo.HeaderContentType, contentType)
|
req.Header.Set(echo.HeaderContentType, contentType)
|
||||||
test.AssertStatusCode(t, http.StatusNotFound, srv, req)
|
test.AssertStatusCode(t, http.StatusNotFound, srv, req)
|
||||||
// URL endpoint should return 404.
|
// URL endpoint should return 404.
|
||||||
body, contentType = test.URLMultipartForm(t, nil)
|
body, contentType = test.URLMultipartForm(t, nil)
|
||||||
req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, urlEndpoint), body)
|
req = httptest.NewRequest(http.MethodPost, urlEndpoint(config), body)
|
||||||
req.Header.Set(echo.HeaderContentType, contentType)
|
req.Header.Set(echo.HeaderContentType, contentType)
|
||||||
test.AssertStatusCode(t, http.StatusNotFound, srv, req)
|
test.AssertStatusCode(t, http.StatusNotFound, srv, req)
|
||||||
// Markdown endpoint should return 404.
|
// Markdown endpoint should return 404.
|
||||||
body, contentType = test.MarkdownMultipartForm(t, nil)
|
body, contentType = test.MarkdownMultipartForm(t, nil)
|
||||||
req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, markdownEndpoint), body)
|
req = httptest.NewRequest(http.MethodPost, markdownEndpoint(config), body)
|
||||||
req.Header.Set(echo.HeaderContentType, contentType)
|
req.Header.Set(echo.HeaderContentType, contentType)
|
||||||
test.AssertStatusCode(t, http.StatusNotFound, srv, req)
|
test.AssertStatusCode(t, http.StatusNotFound, srv, req)
|
||||||
// Office endpoint should return 404.
|
// Office endpoint should return 404.
|
||||||
body, contentType = test.OfficeMultipartForm(t, nil)
|
body, contentType = test.OfficeMultipartForm(t, nil)
|
||||||
req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, officeEndpoint), body)
|
req = httptest.NewRequest(http.MethodPost, officeEndpoint(config), body)
|
||||||
req.Header.Set(echo.HeaderContentType, contentType)
|
req.Header.Set(echo.HeaderContentType, contentType)
|
||||||
test.AssertStatusCode(t, http.StatusNotFound, srv, req)
|
test.AssertStatusCode(t, http.StatusNotFound, srv, req)
|
||||||
// finally...
|
// finally...
|
||||||
os.Setenv(conf.DisableGoogleChromeEnvVar, "0")
|
os.Setenv(conf.DisableGoogleChromeEnvVar, "0")
|
||||||
os.Setenv(conf.DisableUnoconvEnvVar, "0")
|
os.Setenv(conf.DisableUnoconvEnvVar, "0")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCustomRootPath(t *testing.T) {
|
||||||
|
os.Setenv(conf.RootPathEnvVar, "/foo/")
|
||||||
|
config, err := conf.FromEnv()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
srv := New(config)
|
||||||
|
// Ping endpoint should return 200.
|
||||||
|
req := httptest.NewRequest(http.MethodGet, pingEndpoint(config), nil)
|
||||||
|
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
||||||
|
// Merge endpoint should return 200.
|
||||||
|
body, contentType := test.MergeMultipartForm(t, nil)
|
||||||
|
req = httptest.NewRequest(http.MethodPost, mergeEndpoint(config), body)
|
||||||
|
req.Header.Set(echo.HeaderContentType, contentType)
|
||||||
|
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
||||||
|
// HTML endpoint should return 200.
|
||||||
|
body, contentType = test.HTMLMultipartForm(t, nil)
|
||||||
|
req = httptest.NewRequest(http.MethodPost, htmlEndpoint(config), body)
|
||||||
|
req.Header.Set(echo.HeaderContentType, contentType)
|
||||||
|
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
||||||
|
// URL endpoint should return 200.
|
||||||
|
body, contentType = test.URLMultipartForm(t, nil)
|
||||||
|
req = httptest.NewRequest(http.MethodPost, urlEndpoint(config), body)
|
||||||
|
req.Header.Set(echo.HeaderContentType, contentType)
|
||||||
|
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
||||||
|
// Markdown endpoint should return 200.
|
||||||
|
body, contentType = test.MarkdownMultipartForm(t, nil)
|
||||||
|
req = httptest.NewRequest(http.MethodPost, markdownEndpoint(config), body)
|
||||||
|
req.Header.Set(echo.HeaderContentType, contentType)
|
||||||
|
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
||||||
|
// Office endpoint should return 200.
|
||||||
|
body, contentType = test.OfficeMultipartForm(t, nil)
|
||||||
|
req = httptest.NewRequest(http.MethodPost, officeEndpoint(config), body)
|
||||||
|
req.Header.Set(echo.HeaderContentType, contentType)
|
||||||
|
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
||||||
|
// finally...
|
||||||
|
os.Setenv(conf.RootPathEnvVar, "/")
|
||||||
|
}
|
||||||
|
|||||||
@@ -48,8 +48,11 @@ func cmd(logger xlog.Logger) (*exec.Cmd, error) {
|
|||||||
args := []string{
|
args := []string{
|
||||||
"--no-sandbox",
|
"--no-sandbox",
|
||||||
"--headless",
|
"--headless",
|
||||||
// see https://github.com/GoogleChrome/puppeteer/issues/2410.
|
// see https://github.com/thecodingmachine/gotenberg/issues/157.
|
||||||
"--font-render-hinting=medium",
|
"--disable-dev-shm-usage",
|
||||||
|
// See https://github.com/puppeteer/puppeteer/issues/661
|
||||||
|
// and https://github.com/puppeteer/puppeteer/issues/2410.
|
||||||
|
"--font-render-hinting=none",
|
||||||
"--remote-debugging-port=9222",
|
"--remote-debugging-port=9222",
|
||||||
"--disable-gpu",
|
"--disable-gpu",
|
||||||
"--disable-translate",
|
"--disable-translate",
|
||||||
|
|||||||
@@ -34,6 +34,9 @@ const (
|
|||||||
// LogLevelEnvVar contains the name
|
// LogLevelEnvVar contains the name
|
||||||
// of the environment variable "LOG_LEVEL".
|
// of the environment variable "LOG_LEVEL".
|
||||||
LogLevelEnvVar string = "LOG_LEVEL"
|
LogLevelEnvVar string = "LOG_LEVEL"
|
||||||
|
// RootPathEnvVar contains the name
|
||||||
|
// of the environment variable "ROOT_PATH".
|
||||||
|
RootPathEnvVar string = "ROOT_PATH"
|
||||||
// DefaultGoogleChromeRpccBufferSizeEnvVar contains the name
|
// DefaultGoogleChromeRpccBufferSizeEnvVar contains the name
|
||||||
// of the environment variable "DEFAULT_GOOGLE_CHROME_RPCC_BUFFER_SIZE".
|
// of the environment variable "DEFAULT_GOOGLE_CHROME_RPCC_BUFFER_SIZE".
|
||||||
DefaultGoogleChromeRpccBufferSizeEnvVar string = "DEFAULT_GOOGLE_CHROME_RPCC_BUFFER_SIZE"
|
DefaultGoogleChromeRpccBufferSizeEnvVar string = "DEFAULT_GOOGLE_CHROME_RPCC_BUFFER_SIZE"
|
||||||
@@ -51,6 +54,7 @@ type Config struct {
|
|||||||
disableGoogleChrome bool
|
disableGoogleChrome bool
|
||||||
disableUnoconv bool
|
disableUnoconv bool
|
||||||
logLevel xlog.Level
|
logLevel xlog.Level
|
||||||
|
rootPath string
|
||||||
maximumGoogleChromeRpccBufferSize int64
|
maximumGoogleChromeRpccBufferSize int64
|
||||||
defaultGoogleChromeRpccBufferSize int64
|
defaultGoogleChromeRpccBufferSize int64
|
||||||
}
|
}
|
||||||
@@ -68,6 +72,7 @@ func DefaultConfig() Config {
|
|||||||
disableGoogleChrome: false,
|
disableGoogleChrome: false,
|
||||||
disableUnoconv: false,
|
disableUnoconv: false,
|
||||||
logLevel: xlog.InfoLevel,
|
logLevel: xlog.InfoLevel,
|
||||||
|
rootPath: "/",
|
||||||
maximumGoogleChromeRpccBufferSize: 104857600, // ~100 MB
|
maximumGoogleChromeRpccBufferSize: 104857600, // ~100 MB
|
||||||
defaultGoogleChromeRpccBufferSize: 1048576, // 1 MB
|
defaultGoogleChromeRpccBufferSize: 1048576, // 1 MB
|
||||||
}
|
}
|
||||||
@@ -163,6 +168,16 @@ func FromEnv() (Config, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return c, err
|
return c, err
|
||||||
}
|
}
|
||||||
|
rootPath, err := xassert.StringFromEnv(
|
||||||
|
RootPathEnvVar,
|
||||||
|
c.rootPath,
|
||||||
|
xassert.StringStartWith("/"),
|
||||||
|
xassert.StringEndWith("/"),
|
||||||
|
)
|
||||||
|
c.rootPath = rootPath
|
||||||
|
if err != nil {
|
||||||
|
return c, err
|
||||||
|
}
|
||||||
defaultGoogleChromeRpccBufferSize, err := xassert.Int64FromEnv(
|
defaultGoogleChromeRpccBufferSize, err := xassert.Int64FromEnv(
|
||||||
DefaultGoogleChromeRpccBufferSizeEnvVar,
|
DefaultGoogleChromeRpccBufferSizeEnvVar,
|
||||||
c.defaultGoogleChromeRpccBufferSize,
|
c.defaultGoogleChromeRpccBufferSize,
|
||||||
@@ -242,6 +257,12 @@ func (c Config) LogLevel() xlog.Level {
|
|||||||
return c.logLevel
|
return c.logLevel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RootPath returns the rooth path from
|
||||||
|
// the configuration.
|
||||||
|
func (c Config) RootPath() string {
|
||||||
|
return c.rootPath
|
||||||
|
}
|
||||||
|
|
||||||
// MaximumGoogleChromeRpccBufferSize returns the maximum
|
// MaximumGoogleChromeRpccBufferSize returns the maximum
|
||||||
// Google Chrome rpcc buffer size from the configuration.
|
// Google Chrome rpcc buffer size from the configuration.
|
||||||
func (c Config) MaximumGoogleChromeRpccBufferSize() int64 {
|
func (c Config) MaximumGoogleChromeRpccBufferSize() int64 {
|
||||||
|
|||||||
@@ -320,6 +320,29 @@ func TestLogLevelFromEnv(t *testing.T) {
|
|||||||
os.Unsetenv(LogLevelEnvVar)
|
os.Unsetenv(LogLevelEnvVar)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRootPathFromEnv(t *testing.T) {
|
||||||
|
var (
|
||||||
|
expected Config
|
||||||
|
result Config
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
// ROOT_PATH correctly set.
|
||||||
|
os.Setenv(RootPathEnvVar, "/foo/")
|
||||||
|
expected = DefaultConfig()
|
||||||
|
expected.rootPath = "/foo/"
|
||||||
|
result, err = FromEnv()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, expected, result)
|
||||||
|
os.Unsetenv(RootPathEnvVar)
|
||||||
|
// ROOT_PATH wrongly set.
|
||||||
|
os.Setenv(RootPathEnvVar, "foo")
|
||||||
|
expected = DefaultConfig()
|
||||||
|
result, err = FromEnv()
|
||||||
|
test.AssertError(t, err)
|
||||||
|
assert.Equal(t, expected, result)
|
||||||
|
os.Unsetenv(RootPathEnvVar)
|
||||||
|
}
|
||||||
|
|
||||||
func TestDefaultGoogleChromeRpccBufferSizeFromEnv(t *testing.T) {
|
func TestDefaultGoogleChromeRpccBufferSizeFromEnv(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
expected Config
|
expected Config
|
||||||
@@ -368,6 +391,7 @@ func TestGetters(t *testing.T) {
|
|||||||
assert.Equal(t, result.disableGoogleChrome, result.DisableGoogleChrome())
|
assert.Equal(t, result.disableGoogleChrome, result.DisableGoogleChrome())
|
||||||
assert.Equal(t, result.disableUnoconv, result.DisableUnoconv())
|
assert.Equal(t, result.disableUnoconv, result.DisableUnoconv())
|
||||||
assert.Equal(t, result.logLevel, result.LogLevel())
|
assert.Equal(t, result.logLevel, result.LogLevel())
|
||||||
|
assert.Equal(t, result.rootPath, result.RootPath())
|
||||||
assert.Equal(t, result.maximumGoogleChromeRpccBufferSize, result.MaximumGoogleChromeRpccBufferSize())
|
assert.Equal(t, result.maximumGoogleChromeRpccBufferSize, result.MaximumGoogleChromeRpccBufferSize())
|
||||||
assert.Equal(t, result.defaultGoogleChromeRpccBufferSize, result.DefaultGoogleChromeRpccBufferSize())
|
assert.Equal(t, result.defaultGoogleChromeRpccBufferSize, result.DefaultGoogleChromeRpccBufferSize())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package printer
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -30,18 +31,21 @@ type chromePrinter struct {
|
|||||||
// ChromePrinterOptions helps customizing the
|
// ChromePrinterOptions helps customizing the
|
||||||
// Google Chrome Printer behaviour.
|
// Google Chrome Printer behaviour.
|
||||||
type ChromePrinterOptions struct {
|
type ChromePrinterOptions struct {
|
||||||
WaitTimeout float64
|
WaitTimeout float64
|
||||||
WaitDelay float64
|
WaitDelay float64
|
||||||
HeaderHTML string
|
HeaderHTML string
|
||||||
FooterHTML string
|
FooterHTML string
|
||||||
PaperWidth float64
|
PaperWidth float64
|
||||||
PaperHeight float64
|
PaperHeight float64
|
||||||
MarginTop float64
|
MarginTop float64
|
||||||
MarginBottom float64
|
MarginBottom float64
|
||||||
MarginLeft float64
|
MarginLeft float64
|
||||||
MarginRight float64
|
MarginRight float64
|
||||||
Landscape bool
|
Landscape bool
|
||||||
RpccBufferSize int64
|
PageRanges string
|
||||||
|
RpccBufferSize int64
|
||||||
|
CustomHTTPHeaders map[string]string
|
||||||
|
Scale float64
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultChromePrinterOptions returns the default
|
// DefaultChromePrinterOptions returns the default
|
||||||
@@ -49,18 +53,21 @@ type ChromePrinterOptions struct {
|
|||||||
func DefaultChromePrinterOptions(config conf.Config) ChromePrinterOptions {
|
func DefaultChromePrinterOptions(config conf.Config) ChromePrinterOptions {
|
||||||
const defaultHeaderFooterHTML string = "<html><head></head><body></body></html>"
|
const defaultHeaderFooterHTML string = "<html><head></head><body></body></html>"
|
||||||
return ChromePrinterOptions{
|
return ChromePrinterOptions{
|
||||||
WaitTimeout: config.DefaultWaitTimeout(),
|
WaitTimeout: config.DefaultWaitTimeout(),
|
||||||
WaitDelay: 0.0,
|
WaitDelay: 0.0,
|
||||||
HeaderHTML: defaultHeaderFooterHTML,
|
HeaderHTML: defaultHeaderFooterHTML,
|
||||||
FooterHTML: defaultHeaderFooterHTML,
|
FooterHTML: defaultHeaderFooterHTML,
|
||||||
PaperWidth: 8.27,
|
PaperWidth: 8.27,
|
||||||
PaperHeight: 11.7,
|
PaperHeight: 11.7,
|
||||||
MarginTop: 1.0,
|
MarginTop: 1.0,
|
||||||
MarginBottom: 1.0,
|
MarginBottom: 1.0,
|
||||||
MarginLeft: 1.0,
|
MarginLeft: 1.0,
|
||||||
MarginRight: 1.0,
|
MarginRight: 1.0,
|
||||||
Landscape: false,
|
Landscape: false,
|
||||||
RpccBufferSize: config.DefaultGoogleChromeRpccBufferSize(),
|
PageRanges: "",
|
||||||
|
RpccBufferSize: config.DefaultGoogleChromeRpccBufferSize(),
|
||||||
|
CustomHTTPHeaders: make(map[string]string),
|
||||||
|
Scale: 1.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,6 +151,10 @@ func (p chromePrinter) Print(destination string) error {
|
|||||||
if err := p.enableEvents(ctx, targetClient); err != nil {
|
if err := p.enableEvents(ctx, targetClient); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// add custom headers (if any).
|
||||||
|
if err := p.setCustomHTTPHeaders(ctx, targetClient); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
// listen for all events.
|
// listen for all events.
|
||||||
if err := p.listenEvents(ctx, targetClient); err != nil {
|
if err := p.listenEvents(ctx, targetClient); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -156,23 +167,36 @@ func (p chromePrinter) Print(destination string) error {
|
|||||||
} else {
|
} else {
|
||||||
p.logger.DebugOp(op, "no wait delay to apply, moving on...")
|
p.logger.DebugOp(op, "no wait delay to apply, moving on...")
|
||||||
}
|
}
|
||||||
|
printToPdfArgs := page.NewPrintToPDFArgs().
|
||||||
|
SetPaperWidth(p.opts.PaperWidth).
|
||||||
|
SetPaperHeight(p.opts.PaperHeight).
|
||||||
|
SetMarginTop(p.opts.MarginTop).
|
||||||
|
SetMarginBottom(p.opts.MarginBottom).
|
||||||
|
SetMarginLeft(p.opts.MarginLeft).
|
||||||
|
SetMarginRight(p.opts.MarginRight).
|
||||||
|
SetLandscape(p.opts.Landscape).
|
||||||
|
SetDisplayHeaderFooter(true).
|
||||||
|
SetHeaderTemplate(p.opts.HeaderHTML).
|
||||||
|
SetFooterTemplate(p.opts.FooterHTML).
|
||||||
|
SetPrintBackground(true).
|
||||||
|
SetScale(p.opts.Scale)
|
||||||
|
if p.opts.PageRanges != "" {
|
||||||
|
printToPdfArgs.SetPageRanges(p.opts.PageRanges)
|
||||||
|
}
|
||||||
// print the page to PDF.
|
// print the page to PDF.
|
||||||
print, err := targetClient.Page.PrintToPDF(
|
print, err := targetClient.Page.PrintToPDF(
|
||||||
ctx,
|
ctx,
|
||||||
page.NewPrintToPDFArgs().
|
printToPdfArgs,
|
||||||
SetPaperWidth(p.opts.PaperWidth).
|
|
||||||
SetPaperHeight(p.opts.PaperHeight).
|
|
||||||
SetMarginTop(p.opts.MarginTop).
|
|
||||||
SetMarginBottom(p.opts.MarginBottom).
|
|
||||||
SetMarginLeft(p.opts.MarginLeft).
|
|
||||||
SetMarginRight(p.opts.MarginRight).
|
|
||||||
SetLandscape(p.opts.Landscape).
|
|
||||||
SetDisplayHeaderFooter(true).
|
|
||||||
SetHeaderTemplate(p.opts.HeaderHTML).
|
|
||||||
SetFooterTemplate(p.opts.FooterHTML).
|
|
||||||
SetPrintBackground(true),
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// find a way to check it in the handlers?
|
||||||
|
if strings.Contains(err.Error(), "Page range syntax error") {
|
||||||
|
return xerror.Invalid(
|
||||||
|
op,
|
||||||
|
fmt.Sprintf("'%s' is not a valid Google Chrome page ranges", p.opts.PageRanges),
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
if strings.Contains(err.Error(), "rpcc: message too large") {
|
if strings.Contains(err.Error(), "rpcc: message too large") {
|
||||||
return xerror.Invalid(
|
return xerror.Invalid(
|
||||||
op,
|
op,
|
||||||
@@ -247,6 +271,32 @@ func (p chromePrinter) enableEvents(ctx context.Context, client *cdp.Client) err
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p chromePrinter) setCustomHTTPHeaders(ctx context.Context, client *cdp.Client) error {
|
||||||
|
const op string = "printer.chromePrinter.setCustomHTTPHeaders"
|
||||||
|
resolver := func() error {
|
||||||
|
if len(p.opts.CustomHTTPHeaders) == 0 {
|
||||||
|
p.logger.DebugOp(op, "skipping custom HTTP headers as none have been provided...")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
customHTTPHeaders := make(map[string]string)
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
b, err := json.Marshal(customHTTPHeaders)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// should always be called after client.Network.Enable.
|
||||||
|
return client.Network.SetExtraHTTPHeaders(ctx, network.NewSetExtraHTTPHeadersArgs(b))
|
||||||
|
}
|
||||||
|
if err := resolver(); err != nil {
|
||||||
|
return xerror.New(op, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (p chromePrinter) listenEvents(ctx context.Context, client *cdp.Client) error {
|
func (p chromePrinter) listenEvents(ctx context.Context, client *cdp.Client) error {
|
||||||
const op string = "printer.chromePrinter.listenEvents"
|
const op string = "printer.chromePrinter.listenEvents"
|
||||||
resolver := func() error {
|
resolver := func() error {
|
||||||
|
|||||||
@@ -38,6 +38,26 @@ func TestHTMLPrinter(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
err = os.RemoveAll(dest)
|
err = os.RemoveAll(dest)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
// options with a page ranges.
|
||||||
|
opts = DefaultChromePrinterOptions(config)
|
||||||
|
opts.PageRanges = "1"
|
||||||
|
p = NewHTMLPrinter(logger, fpath, opts)
|
||||||
|
dest = test.GenerateDestination()
|
||||||
|
err = p.Print(dest)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
err = os.RemoveAll(dest)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
// should not be OK as options have
|
||||||
|
// a wrong page ranges.
|
||||||
|
opts = DefaultChromePrinterOptions(config)
|
||||||
|
opts.PageRanges = "foo"
|
||||||
|
p = NewHTMLPrinter(logger, fpath, opts)
|
||||||
|
dest = test.GenerateDestination()
|
||||||
|
err = p.Print(dest)
|
||||||
|
test.AssertError(t, err)
|
||||||
|
assert.Equal(t, xerror.InvalidCode, xerror.Code(err))
|
||||||
|
err = os.RemoveAll(dest)
|
||||||
|
assert.Nil(t, err)
|
||||||
// should not be OK as context.Context
|
// should not be OK as context.Context
|
||||||
// should timeout.
|
// should timeout.
|
||||||
opts = DefaultChromePrinterOptions(config)
|
opts = DefaultChromePrinterOptions(config)
|
||||||
|
|||||||
@@ -40,6 +40,28 @@ func TestMarkdownPrinter(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
err = os.RemoveAll(dest)
|
err = os.RemoveAll(dest)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
// options with a page ranges.
|
||||||
|
opts = DefaultChromePrinterOptions(config)
|
||||||
|
opts.PageRanges = "1"
|
||||||
|
p, err = NewMarkdownPrinter(logger, fpath, opts)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
dest = test.GenerateDestination()
|
||||||
|
err = p.Print(dest)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
err = os.RemoveAll(dest)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
// should not be OK as options have
|
||||||
|
// a wrong page ranges.
|
||||||
|
opts = DefaultChromePrinterOptions(config)
|
||||||
|
opts.PageRanges = "foo"
|
||||||
|
p, err = NewMarkdownPrinter(logger, fpath, opts)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
dest = test.GenerateDestination()
|
||||||
|
err = p.Print(dest)
|
||||||
|
test.AssertError(t, err)
|
||||||
|
assert.Equal(t, xerror.InvalidCode, xerror.Code(err))
|
||||||
|
err = os.RemoveAll(dest)
|
||||||
|
assert.Nil(t, err)
|
||||||
// should not be OK as context.Context
|
// should not be OK as context.Context
|
||||||
// should timeout.
|
// should timeout.
|
||||||
opts = DefaultChromePrinterOptions(config)
|
opts = DefaultChromePrinterOptions(config)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package printer
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"sort"
|
||||||
|
|
||||||
"github.com/thecodingmachine/gotenberg/internal/pkg/conf"
|
"github.com/thecodingmachine/gotenberg/internal/pkg/conf"
|
||||||
"github.com/thecodingmachine/gotenberg/internal/pkg/xcontext"
|
"github.com/thecodingmachine/gotenberg/internal/pkg/xcontext"
|
||||||
@@ -54,6 +55,8 @@ func (p mergePrinter) Print(destination string) error {
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
p.ctx = ctx
|
p.ctx = ctx
|
||||||
}
|
}
|
||||||
|
// see https://github.com/thecodingmachine/gotenberg/issues/139.
|
||||||
|
sort.Strings(p.fpaths)
|
||||||
p.logger.DebugfOp(op, "merging '%v'...", p.fpaths)
|
p.logger.DebugfOp(op, "merging '%v'...", p.fpaths)
|
||||||
resolver := func() error {
|
resolver := func() error {
|
||||||
var args []string
|
var args []string
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/phayes/freeport"
|
"github.com/phayes/freeport"
|
||||||
"github.com/thecodingmachine/gotenberg/internal/pkg/conf"
|
"github.com/thecodingmachine/gotenberg/internal/pkg/conf"
|
||||||
@@ -26,6 +28,7 @@ type officePrinter struct {
|
|||||||
type OfficePrinterOptions struct {
|
type OfficePrinterOptions struct {
|
||||||
WaitTimeout float64
|
WaitTimeout float64
|
||||||
Landscape bool
|
Landscape bool
|
||||||
|
PageRanges string
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultOfficePrinterOptions returns the default
|
// DefaultOfficePrinterOptions returns the default
|
||||||
@@ -34,6 +37,7 @@ func DefaultOfficePrinterOptions(config conf.Config) OfficePrinterOptions {
|
|||||||
return OfficePrinterOptions{
|
return OfficePrinterOptions{
|
||||||
WaitTimeout: config.DefaultWaitTimeout(),
|
WaitTimeout: config.DefaultWaitTimeout(),
|
||||||
Landscape: false,
|
Landscape: false,
|
||||||
|
PageRanges: "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,13 +57,15 @@ func (p officePrinter) Print(destination string) error {
|
|||||||
ctx, cancel := xcontext.WithTimeout(p.logger, p.opts.WaitTimeout)
|
ctx, cancel := xcontext.WithTimeout(p.logger, p.opts.WaitTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
resolver := func() error {
|
resolver := func() error {
|
||||||
|
// see https://github.com/thecodingmachine/gotenberg/issues/139.
|
||||||
|
sort.Strings(p.fpaths)
|
||||||
fpaths := make([]string, len(p.fpaths))
|
fpaths := make([]string, len(p.fpaths))
|
||||||
dirPath := filepath.Dir(destination)
|
dirPath := filepath.Dir(destination)
|
||||||
for i, fpath := range p.fpaths {
|
for i, fpath := range p.fpaths {
|
||||||
baseFilename := xrand.Get()
|
baseFilename := xrand.Get()
|
||||||
tmpDest := fmt.Sprintf("%s/%d%s.pdf", dirPath, i, baseFilename)
|
tmpDest := fmt.Sprintf("%s/%d%s.pdf", dirPath, i, baseFilename)
|
||||||
p.logger.DebugfOp(op, "converting '%s' to PDF...", fpath)
|
p.logger.DebugfOp(op, "converting '%s' to PDF...", fpath)
|
||||||
if err := unoconv(ctx, p.logger, fpath, tmpDest, p.opts); err != nil {
|
if err := p.unoconv(ctx, fpath, tmpDest); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
p.logger.DebugfOp(op, "'%s.pdf' created", baseFilename)
|
p.logger.DebugfOp(op, "'%s.pdf' created", baseFilename)
|
||||||
@@ -85,7 +91,7 @@ func (p officePrinter) Print(destination string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func unoconv(ctx context.Context, logger xlog.Logger, fpath, destination string, opts OfficePrinterOptions) error {
|
func (p officePrinter) unoconv(ctx context.Context, fpath, destination string) error {
|
||||||
const op string = "printer.unoconv"
|
const op string = "printer.unoconv"
|
||||||
resolver := func() error {
|
resolver := func() error {
|
||||||
port, err := freeport.GetFreePort()
|
port, err := freeport.GetFreePort()
|
||||||
@@ -100,11 +106,25 @@ func unoconv(ctx context.Context, logger xlog.Logger, fpath, destination string,
|
|||||||
"--format",
|
"--format",
|
||||||
"pdf",
|
"pdf",
|
||||||
}
|
}
|
||||||
if opts.Landscape {
|
if p.opts.Landscape {
|
||||||
args = append(args, "--printer", "PaperOrientation=landscape")
|
args = append(args, "--printer", "PaperOrientation=landscape")
|
||||||
}
|
}
|
||||||
|
if p.opts.PageRanges != "" {
|
||||||
|
args = append(args, "--export", fmt.Sprintf("PageRange=%s", p.opts.PageRanges))
|
||||||
|
}
|
||||||
args = append(args, "--output", destination, fpath)
|
args = append(args, "--output", destination, fpath)
|
||||||
return xexec.Run(ctx, logger, "unoconv", args...)
|
if err := xexec.Run(ctx, p.logger, "unoconv", args...); 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(
|
||||||
|
op,
|
||||||
|
fmt.Sprintf("'%s' is not a valid LibreOffice page ranges", p.opts.PageRanges),
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
if err := resolver(); err != nil {
|
if err := resolver(); err != nil {
|
||||||
return xerror.New(op, err)
|
return xerror.New(op, err)
|
||||||
|
|||||||
@@ -46,6 +46,26 @@ func TestOfficePrinter(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
err = os.RemoveAll(dest)
|
err = os.RemoveAll(dest)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
// options with page ranges.
|
||||||
|
opts = DefaultOfficePrinterOptions(config)
|
||||||
|
opts.PageRanges = "1-1"
|
||||||
|
p = NewOfficePrinter(logger, []string{fpaths[0]}, opts)
|
||||||
|
dest = test.GenerateDestination()
|
||||||
|
err = p.Print(dest)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
err = os.RemoveAll(dest)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
// should not be OK as options have
|
||||||
|
// a wrong page ranges.
|
||||||
|
opts = DefaultOfficePrinterOptions(config)
|
||||||
|
opts.PageRanges = "foo"
|
||||||
|
p = NewOfficePrinter(logger, []string{fpaths[0]}, opts)
|
||||||
|
dest = test.GenerateDestination()
|
||||||
|
err = p.Print(dest)
|
||||||
|
test.AssertError(t, err)
|
||||||
|
assert.Equal(t, xerror.InvalidCode, xerror.Code(err))
|
||||||
|
err = os.RemoveAll(dest)
|
||||||
|
assert.Nil(t, err)
|
||||||
// should not be OK as context.Context
|
// should not be OK as context.Context
|
||||||
// should timeout.
|
// should timeout.
|
||||||
opts = DefaultOfficePrinterOptions(config)
|
opts = DefaultOfficePrinterOptions(config)
|
||||||
|
|||||||
@@ -38,6 +38,26 @@ func TestURLPrinter(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
err = os.RemoveAll(dest)
|
err = os.RemoveAll(dest)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
// options with a pages ranges.
|
||||||
|
opts = DefaultChromePrinterOptions(config)
|
||||||
|
opts.PageRanges = "1"
|
||||||
|
p = NewURLPrinter(logger, URL, opts)
|
||||||
|
dest = test.GenerateDestination()
|
||||||
|
err = p.Print(dest)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
err = os.RemoveAll(dest)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
// should not be OK as options have
|
||||||
|
// a wrong page ranges.
|
||||||
|
opts = DefaultChromePrinterOptions(config)
|
||||||
|
opts.PageRanges = "foo"
|
||||||
|
p = NewURLPrinter(logger, URL, opts)
|
||||||
|
dest = test.GenerateDestination()
|
||||||
|
err = p.Print(dest)
|
||||||
|
test.AssertError(t, err)
|
||||||
|
assert.Equal(t, xerror.InvalidCode, xerror.Code(err))
|
||||||
|
err = os.RemoveAll(dest)
|
||||||
|
assert.Nil(t, err)
|
||||||
// should not be OK as context.Context
|
// should not be OK as context.Context
|
||||||
// should timeout.
|
// should timeout.
|
||||||
opts = DefaultChromePrinterOptions(config)
|
opts = DefaultChromePrinterOptions(config)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package xassert
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/thecodingmachine/gotenberg/internal/pkg/xerror"
|
"github.com/thecodingmachine/gotenberg/internal/pkg/xerror"
|
||||||
)
|
)
|
||||||
@@ -54,7 +55,67 @@ func StringOneOf(values []string) RuleString {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ruleStringStartWith struct {
|
||||||
|
*baseRuleString
|
||||||
|
startWith string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r ruleStringStartWith) validate() error {
|
||||||
|
const op string = "xassert.ruleStringStartWith.validate"
|
||||||
|
if strings.HasPrefix(r.value, r.startWith) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return xerror.Invalid(
|
||||||
|
op,
|
||||||
|
fmt.Sprintf("'%s' should start with '%s', got '%s'", r.key, r.startWith, r.value),
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
StringStartWith returns a RuleString for
|
||||||
|
validating that a string starts with
|
||||||
|
given string.
|
||||||
|
*/
|
||||||
|
func StringStartWith(startWith string) RuleString {
|
||||||
|
return ruleStringStartWith{
|
||||||
|
&baseRuleString{},
|
||||||
|
startWith,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ruleStringEndWith struct {
|
||||||
|
*baseRuleString
|
||||||
|
endWith string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r ruleStringEndWith) validate() error {
|
||||||
|
const op string = "xassert.ruleStringEndWith.validate"
|
||||||
|
if strings.HasSuffix(r.value, r.endWith) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return xerror.Invalid(
|
||||||
|
op,
|
||||||
|
fmt.Sprintf("'%s' should end with '%s', got '%s'", r.key, r.endWith, r.value),
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
StringEndWith returns a RuleString for
|
||||||
|
validating that a string ends with
|
||||||
|
given string.
|
||||||
|
*/
|
||||||
|
func StringEndWith(endWith string) RuleString {
|
||||||
|
return ruleStringEndWith{
|
||||||
|
&baseRuleString{},
|
||||||
|
endWith,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Compile-time checks to ensure type implements desired interfaces.
|
// Compile-time checks to ensure type implements desired interfaces.
|
||||||
var (
|
var (
|
||||||
_ = RuleString(new(ruleStringOneOf))
|
_ = RuleString(new(ruleStringOneOf))
|
||||||
|
_ = RuleString(new(ruleStringStartWith))
|
||||||
|
_ = RuleString(new(ruleStringEndWith))
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -18,3 +18,27 @@ func TestStringOfOne(t *testing.T) {
|
|||||||
err = rule.validate()
|
err = rule.validate()
|
||||||
test.AssertError(t, err)
|
test.AssertError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStringStartWith(t *testing.T) {
|
||||||
|
rule := StringStartWith("foo")
|
||||||
|
// should be OK.
|
||||||
|
rule.with("FOO", "foobarfoo")
|
||||||
|
err := rule.validate()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
// should not be OK.
|
||||||
|
rule.with("FOO", "qux")
|
||||||
|
err = rule.validate()
|
||||||
|
test.AssertError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringEndWith(t *testing.T) {
|
||||||
|
rule := StringEndWith("foo")
|
||||||
|
// should be OK.
|
||||||
|
rule.with("FOO", "foobarfoo")
|
||||||
|
err := rule.validate()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
// should not be OK.
|
||||||
|
rule.with("FOO", "qux")
|
||||||
|
err = rule.validate()
|
||||||
|
test.AssertError(t, err)
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ set -e
|
|||||||
|
|
||||||
GOLANG_VERSION="$1"
|
GOLANG_VERSION="$1"
|
||||||
TINI_VERSION="$2"
|
TINI_VERSION="$2"
|
||||||
DOCKER_REPOSITORY="$3"
|
DOCKER_REGISTRY="$3"
|
||||||
VERSION="$4"
|
VERSION="$4"
|
||||||
DOCKER_USER="$5"
|
DOCKER_USER="$5"
|
||||||
DOCKER_PASSWORD="$6"
|
DOCKER_PASSWORD="$6"
|
||||||
@@ -23,13 +23,13 @@ fi
|
|||||||
docker build \
|
docker build \
|
||||||
--build-arg VERSION=${VERSION} \
|
--build-arg VERSION=${VERSION} \
|
||||||
--build-arg TINI_VERSION=${TINI_VERSION} \
|
--build-arg TINI_VERSION=${TINI_VERSION} \
|
||||||
-t ${DOCKER_REPOSITORY}/gotenberg:latest \
|
-t ${DOCKER_REGISTRY}/gotenberg:latest \
|
||||||
-t ${DOCKER_REPOSITORY}/gotenberg:${SEMVER[0]} \
|
-t ${DOCKER_REGISTRY}/gotenberg:${SEMVER[0]} \
|
||||||
-t ${DOCKER_REPOSITORY}/gotenberg:${SEMVER[0]}.${SEMVER[1]} \
|
-t ${DOCKER_REGISTRY}/gotenberg:${SEMVER[0]}.${SEMVER[1]} \
|
||||||
-t ${DOCKER_REPOSITORY}/gotenberg:${SEMVER[0]}.${SEMVER[1]}.${SEMVER[2]} \
|
-t ${DOCKER_REGISTRY}/gotenberg:${SEMVER[0]}.${SEMVER[1]}.${SEMVER[2]} \
|
||||||
-f build/package/Dockerfile .
|
-f build/package/Dockerfile .
|
||||||
|
|
||||||
docker push "${DOCKER_REPOSITORY}/gotenberg:latest"
|
docker push "${DOCKER_REGISTRY}/gotenberg:latest"
|
||||||
docker push "${DOCKER_REPOSITORY}/gotenberg:${SEMVER[0]}"
|
docker push "${DOCKER_REGISTRY}/gotenberg:${SEMVER[0]}"
|
||||||
docker push "${DOCKER_REPOSITORY}/gotenberg:${SEMVER[0]}.${SEMVER[1]}"
|
docker push "${DOCKER_REGISTRY}/gotenberg:${SEMVER[0]}.${SEMVER[1]}"
|
||||||
docker push "${DOCKER_REPOSITORY}/gotenberg:${SEMVER[0]}.${SEMVER[1]}.${SEMVER[2]}"
|
docker push "${DOCKER_REGISTRY}/gotenberg:${SEMVER[0]}.${SEMVER[1]}.${SEMVER[2]}"
|
||||||
@@ -2,15 +2,15 @@
|
|||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
DOCKER_REPOSITORY="$1"
|
DOCKER_REGISTRY="$1"
|
||||||
CODE_COVERAGE="$2"
|
CODE_COVERAGE="$2"
|
||||||
|
|
||||||
touch "$PWD/coverage.txt"
|
touch "$PWD/coverage.txt"
|
||||||
chmod 777 "$PWD/coverage.txt"
|
chmod 777 "$PWD/coverage.txt"
|
||||||
docker build -t "$DOCKER_REPOSITORY/gotenberg:tests" -f build/tests/Dockerfile .
|
docker build -t "$DOCKER_REGISTRY/gotenberg:tests" -f build/tests/Dockerfile .
|
||||||
|
|
||||||
if [ "$CODE_COVERAGE" = "1" ]; then
|
if [ "$CODE_COVERAGE" = "1" ]; then
|
||||||
docker run --rm -e "CODE_COVERAGE=$CODE_COVERAGE" -v "$PWD/coverage.txt:/gotenberg/tests/coverage.txt" "$DOCKER_REPOSITORY/gotenberg:tests"
|
docker run --rm -e "CODE_COVERAGE=$CODE_COVERAGE" -v "$PWD/coverage.txt:/gotenberg/tests/coverage.txt" "$DOCKER_REGISTRY/gotenberg:tests"
|
||||||
else
|
else
|
||||||
docker run --rm -e "CODE_COVERAGE=$CODE_COVERAGE" "$DOCKER_REPOSITORY/gotenberg:tests"
|
docker run --rm -e "CODE_COVERAGE=$CODE_COVERAGE" "$DOCKER_REGISTRY/gotenberg:tests"
|
||||||
fi
|
fi
|
||||||
@@ -79,7 +79,7 @@ func multipartForm(
|
|||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
}
|
}
|
||||||
if kind == "url" {
|
if kind == "url" {
|
||||||
err := writer.WriteField("remoteURL", "http://google.com")
|
err := writer.WriteField("remoteURL", "https://google.com")
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
}
|
}
|
||||||
for k, v := range formValues {
|
for k, v := range formValues {
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ func OfficeFpaths(t *testing.T) []string {
|
|||||||
fpath(t, "office", "document.docx"),
|
fpath(t, "office", "document.docx"),
|
||||||
fpath(t, "office", "document.rtf"),
|
fpath(t, "office", "document.rtf"),
|
||||||
fpath(t, "office", "document.txt"),
|
fpath(t, "office", "document.txt"),
|
||||||
|
fpath(t, "office", "document_with_special_éà.txt"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
3
test/testdata/office/document_with_special_éà.txt
vendored
Normal file
3
test/testdata/office/document_with_special_éà.txt
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
Gutenberg
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
|
||||||
Reference in New Issue
Block a user