mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-13 02:42:14 +01:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f90442cfb1 | ||
|
|
2a44c0f65a | ||
|
|
cd8d2b5e95 | ||
|
|
8d89b2e80a | ||
|
|
ab475d65ee | ||
|
|
fa9963a737 | ||
|
|
d65d9c9a4c | ||
|
|
6f8c7a613f |
4
Makefile
4
Makefile
@@ -15,6 +15,7 @@ DEFAULT_LISTEN_PORT=3000
|
|||||||
DISABLE_GOOGLE_CHROME=0
|
DISABLE_GOOGLE_CHROME=0
|
||||||
DISABLE_UNOCONV=0
|
DISABLE_UNOCONV=0
|
||||||
LOG_LEVEL=INFO
|
LOG_LEVEL=INFO
|
||||||
|
DEFAULT_GOOGLE_CHROME_RPCC_BUFFER_SIZE=1048576
|
||||||
|
|
||||||
# build the base Docker image.
|
# build the base Docker image.
|
||||||
base:
|
base:
|
||||||
@@ -43,6 +44,7 @@ tests:
|
|||||||
|
|
||||||
# generate documentation.
|
# generate documentation.
|
||||||
doc:
|
doc:
|
||||||
|
make workspace
|
||||||
docker build -t $(DOCKER_REPOSITORY)/gotenberg:docs -f build/docs/Dockerfile .
|
docker build -t $(DOCKER_REPOSITORY)/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_REPOSITORY)/gotenberg:docs
|
||||||
|
|
||||||
@@ -53,7 +55,7 @@ image:
|
|||||||
|
|
||||||
# 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) -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 DEFAULT_GOOGLE_CHROME_RPCC_BUFFER_SIZE=$(DEFAULT_GOOGLE_CHROME_RPCC_BUFFER_SIZE) -p "$(DEFAULT_LISTEN_PORT):$(DEFAULT_LISTEN_PORT)" $(DOCKER_REPOSITORY)/gotenberg:$(VERSION)
|
||||||
|
|
||||||
# publish Gotenberg images according to version.
|
# publish Gotenberg images according to version.
|
||||||
publish:
|
publish:
|
||||||
|
|||||||
@@ -33,6 +33,19 @@ It takes the strings `"0"` or `"1"` as value where `1` means `true`
|
|||||||
> If Google Chrome is disabled, the following conversions will **not** be available anymore:
|
> If Google Chrome is disabled, the following conversions will **not** be available anymore:
|
||||||
> [HTML](#html), [URL](#url) and [Markdown](#markdown)
|
> [HTML](#html), [URL](#url) and [Markdown](#markdown)
|
||||||
|
|
||||||
|
## Default Google Chrome rpcc buffer size
|
||||||
|
|
||||||
|
When performing a [HTML](#html), [URL](#url) or [Markdown](#markdown) conversion, the API might return
|
||||||
|
a `400` HTTP code with the message `increase the Google Chrome rpcc buffer size`.
|
||||||
|
|
||||||
|
If so, you may increase this buffer size with the environment variable `DEFAULT_GOOGLE_CHROME_RPCC_BUFFER_SIZE`.
|
||||||
|
|
||||||
|
It takes a string representation of an int as value (e.g. `"1048576"` for 1 MB).
|
||||||
|
The hard limit is 100 MB and is defined by Google Chrome itself.
|
||||||
|
|
||||||
|
> The default Google Chrome rpcc buffer size may also be overridden per request thanks to the form field `googleChromeRpccBufferSize`.
|
||||||
|
> See the [rpcc buffer size section](#html.rpcc_buffer_size).
|
||||||
|
|
||||||
## Disable LibreOffice (unoconv)
|
## Disable LibreOffice (unoconv)
|
||||||
|
|
||||||
You may also disable LibreOffice (unoconv) with `DISABLE_UNOCONV`.
|
You may also disable LibreOffice (unoconv) with `DISABLE_UNOCONV`.
|
||||||
|
|||||||
@@ -95,14 +95,14 @@ The following classes allow you to inject printing values:
|
|||||||
* `pageNumber`: current page number
|
* `pageNumber`: current page number
|
||||||
* `totalPage`: total pages in the document
|
* `totalPage`: total pages in the document
|
||||||
|
|
||||||
> **Attention:** the CSS properties are independant of the ones used in the `index.html` file.
|
There are some limitations:
|
||||||
> Also, `footer.html` CSS properties override the ones from `header.html`.
|
|
||||||
|
|
||||||
For images, the only solution currently is to use a `base64` encoded source:
|
* JavaScript is not executed
|
||||||
|
* external resources are not loaded
|
||||||
```html
|
* the CSS properties are independant of the ones used in the `index.html` file
|
||||||
<img src="data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAAUA AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO 9TXL0Y4OHwAAAABJRU5ErkJggg==" alt="Red dot" />
|
* `footer.html` CSS properties override the ones from `header.html`
|
||||||
```
|
* 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... />`)
|
||||||
|
|
||||||
### cURL
|
### cURL
|
||||||
|
|
||||||
@@ -256,7 +256,7 @@ $ curl --request POST \
|
|||||||
--header 'Content-Type: multipart/form-data' \
|
--header 'Content-Type: multipart/form-data' \
|
||||||
--form files=@index.html \
|
--form files=@index.html \
|
||||||
--form paperWidth=8.27 \
|
--form paperWidth=8.27 \
|
||||||
--form paperHeight=11.27 \
|
--form paperHeight=11.69 \
|
||||||
--form marginTop=0 \
|
--form marginTop=0 \
|
||||||
--form marginBottom=0 \
|
--form marginBottom=0 \
|
||||||
--form marginLeft=0 \
|
--form marginLeft=0 \
|
||||||
@@ -347,3 +347,55 @@ $request->setWaitDelay(5.5);
|
|||||||
$dest = "result.pdf";
|
$dest = "result.pdf";
|
||||||
$client->store($request, $dest);
|
$client->store($request, $dest);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Rpcc buffer size
|
||||||
|
|
||||||
|
The API might return a `400` HTTP code with the message `increase the Google Chrome rpcc buffer size`.
|
||||||
|
|
||||||
|
If so, you may increase this buffer size with a form field named `googleChromeRpccBufferSize`.
|
||||||
|
|
||||||
|
It takes an int as value (e.g. `1048576` for 1 MB).
|
||||||
|
The hard limit is 100 MB and is defined by Google Chrome itself.
|
||||||
|
|
||||||
|
> You may also define this value globally: see the [environment variables](#environment_variables.default_google_chrome_rpcc_buffer_size) section.
|
||||||
|
|
||||||
|
### cURL
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ curl --request POST \
|
||||||
|
--url http://localhost:3000/convert/html \
|
||||||
|
--header 'Content-Type: multipart/form-data' \
|
||||||
|
--form files=@index.html \
|
||||||
|
--form googleChromeRpccBufferSize=1048576 \
|
||||||
|
-o result.pdf
|
||||||
|
```
|
||||||
|
|
||||||
|
### Go
|
||||||
|
|
||||||
|
```golang
|
||||||
|
import "github.com/thecodingmachine/gotenberg-go-client/v6"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
c := &gotenberg.Client{Hostname: "http://localhost:3000"}
|
||||||
|
req, _ := gotenberg.NewHTMLRequest("index.html")
|
||||||
|
req.GoogleChromeRpccBufferSize(1048576)
|
||||||
|
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', 'index.html');
|
||||||
|
$request = new HTMLRequest($index);
|
||||||
|
$request->setGoogleChromeRpccBufferSize(1048576);
|
||||||
|
$dest = "result.pdf";
|
||||||
|
$client->store($request, $dest);
|
||||||
|
```
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ Indeed, Google Chrome misbehaves if there are too many concurrent conversions.
|
|||||||
|
|
||||||
**The more concurrent requests, the more `504` HTTP codes the API will return.**
|
**The more concurrent requests, the more `504` HTTP codes the API will return.**
|
||||||
|
|
||||||
> See our [load testing use case](../loadtesting) for more details about the API behaviour under heavy load.
|
> See our [load testing use case](https://github.com/thecodingmachine/gotenberg/tree/master/loadtesting) for more details about the API behaviour under heavy load.
|
||||||
|
|
||||||
## Strategies
|
## Strategies
|
||||||
|
|
||||||
|
|||||||
@@ -9,5 +9,9 @@ If you wish to use more fonts, you will have to create your own image:
|
|||||||
```Dockerfile
|
```Dockerfile
|
||||||
FROM thecodingmachine/gotenberg:6
|
FROM thecodingmachine/gotenberg:6
|
||||||
|
|
||||||
|
USER root
|
||||||
|
|
||||||
RUN apt-get -y install yourfonts
|
RUN apt-get -y install yourfonts
|
||||||
|
|
||||||
|
USER gotenberg
|
||||||
```
|
```
|
||||||
|
|||||||
101
docs/index.html
101
docs/index.html
@@ -263,6 +263,23 @@ for disabling Google Chrome.</p>
|
|||||||
<a href="#html">HTML</a>, <a href="#url">URL</a> and <a href="#markdown">Markdown</a></p>
|
<a href="#html">HTML</a>, <a href="#url">URL</a> and <a href="#markdown">Markdown</a></p>
|
||||||
</blockquote>
|
</blockquote>
|
||||||
|
|
||||||
|
<h2 class="Heading"><a class="Anchor" aria-hidden="true" id="environment_variables.default_google_chrome_rpcc_buffer_size" href="#environment_variables.default_google_chrome_rpcc_buffer_size">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>
|
||||||
|
</a>Default Google Chrome rpcc buffer size</h2>
|
||||||
|
|
||||||
|
<p>When performing a <a href="#html">HTML</a>, <a href="#url">URL</a> or <a href="#markdown">Markdown</a> conversion, the API might return
|
||||||
|
a <code>400</code> HTTP code with the message <code>increase the Google Chrome rpcc buffer size</code>.</p>
|
||||||
|
|
||||||
|
<p>If so, you may increase this buffer size with the environment variable <code>DEFAULT_GOOGLE_CHROME_RPCC_BUFFER_SIZE</code>.</p>
|
||||||
|
|
||||||
|
<p>It takes a string representation of an int as value (e.g. <code>"1048576"</code> for 1 MB).
|
||||||
|
The hard limit is 100 MB and is defined by Google Chrome itself.</p>
|
||||||
|
|
||||||
|
<blockquote>
|
||||||
|
<p>The default Google Chrome rpcc buffer size may also be overridden per request thanks to the form field <code>googleChromeRpccBufferSize</code>.
|
||||||
|
See the <a href="#html.rpcc_buffer_size">rpcc buffer size section</a>.</p>
|
||||||
|
</blockquote>
|
||||||
|
|
||||||
<h2 class="Heading"><a class="Anchor" aria-hidden="true" id="environment_variables.disable_libre_office_unoconv" href="#environment_variables.disable_libre_office_unoconv">
|
<h2 class="Heading"><a class="Anchor" aria-hidden="true" id="environment_variables.disable_libre_office_unoconv" href="#environment_variables.disable_libre_office_unoconv">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>
|
||||||
</a>Disable LibreOffice (unoconv)</h2>
|
</a>Disable LibreOffice (unoconv)</h2>
|
||||||
@@ -449,15 +466,16 @@ Respectively, a file named <code>header.html</code> and <code>footer.html</code>
|
|||||||
<li><code>totalPage</code>: total pages in the document</li>
|
<li><code>totalPage</code>: total pages in the document</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<blockquote>
|
<p>There are some limitations:</p>
|
||||||
<p><strong>Attention:</strong> the CSS properties are independant of the ones used in the <code>index.html</code> file.
|
|
||||||
Also, <code>footer.html</code> CSS properties override the ones from <code>header.html</code>.</p>
|
|
||||||
</blockquote>
|
|
||||||
|
|
||||||
<p>For images, the only solution currently is to use a <code>base64</code> encoded source:</p>
|
<ul>
|
||||||
|
<li>JavaScript is not executed</li>
|
||||||
<pre class="chroma"><span class="p"><</span><span class="nt">img</span> <span class="na">src</span><span class="o">=</span><span class="s">"data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAAUA AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO 9TXL0Y4OHwAAAABJRU5ErkJggg=="</span> <span class="na">alt</span><span class="o">=</span><span class="s">"Red dot"</span> <span class="p">/></span>
|
<li>external resources are not loaded</li>
|
||||||
</pre>
|
<li>the CSS properties are independant of the ones used in the <code>index.html</code> file</li>
|
||||||
|
<li><code>footer.html</code> CSS properties override the ones from <code>header.html</code></li>
|
||||||
|
<li>only fonts installed in the Docker image are loaded (see the <a href="#fonts">fonts section</a>)</li>
|
||||||
|
<li>images only work using a <code>base64</code> encoded source (<code><img src="data:image/png;base64, iVBORw0K... /></code>)</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
<h3 class="Heading"><a class="Anchor" aria-hidden="true" id="html.header_and_footer.c_url" href="#html.header_and_footer.c_url">
|
<h3 class="Heading"><a class="Anchor" aria-hidden="true" id="html.header_and_footer.c_url" href="#html.header_and_footer.c_url">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>
|
||||||
@@ -624,7 +642,7 @@ $client->store($request, $dest);
|
|||||||
</span><span class="se"></span> --header <span class="s1">'Content-Type: multipart/form-data'</span> <span class="se">\
|
</span><span class="se"></span> --header <span class="s1">'Content-Type: multipart/form-data'</span> <span class="se">\
|
||||||
</span><span class="se"></span> --form <span class="nv">files</span><span class="o">=</span>@index.html <span class="se">\
|
</span><span class="se"></span> --form <span class="nv">files</span><span class="o">=</span>@index.html <span class="se">\
|
||||||
</span><span class="se"></span> --form <span class="nv">paperWidth</span><span class="o">=</span><span class="m">8</span>.27 <span class="se">\
|
</span><span class="se"></span> --form <span class="nv">paperWidth</span><span class="o">=</span><span class="m">8</span>.27 <span class="se">\
|
||||||
</span><span class="se"></span> --form <span class="nv">paperHeight</span><span class="o">=</span><span class="m">11</span>.27 <span class="se">\
|
</span><span class="se"></span> --form <span class="nv">paperHeight</span><span class="o">=</span><span class="m">11</span>.69 <span class="se">\
|
||||||
</span><span class="se"></span> --form <span class="nv">marginTop</span><span class="o">=</span><span class="m">0</span> <span class="se">\
|
</span><span class="se"></span> --form <span class="nv">marginTop</span><span class="o">=</span><span class="m">0</span> <span class="se">\
|
||||||
</span><span class="se"></span> --form <span class="nv">marginBottom</span><span class="o">=</span><span class="m">0</span> <span class="se">\
|
</span><span class="se"></span> --form <span class="nv">marginBottom</span><span class="o">=</span><span class="m">0</span> <span class="se">\
|
||||||
</span><span class="se"></span> --form <span class="nv">marginLeft</span><span class="o">=</span><span class="m">0</span> <span class="se">\
|
</span><span class="se"></span> --form <span class="nv">marginLeft</span><span class="o">=</span><span class="m">0</span> <span class="se">\
|
||||||
@@ -723,6 +741,65 @@ $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);
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h2 class="Heading"><a class="Anchor" aria-hidden="true" id="html.rpcc_buffer_size" href="#html.rpcc_buffer_size">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>
|
||||||
|
</a>Rpcc buffer size</h2>
|
||||||
|
|
||||||
|
<p>The API might return a <code>400</code> HTTP code with the message <code>increase the Google Chrome rpcc buffer size</code>.</p>
|
||||||
|
|
||||||
|
<p>If so, you may increase this buffer size with a form field named <code>googleChromeRpccBufferSize</code>.</p>
|
||||||
|
|
||||||
|
<p>It takes an int as value (e.g. <code>1048576</code> for 1 MB).
|
||||||
|
The hard limit is 100 MB and is defined by Google Chrome itself.</p>
|
||||||
|
|
||||||
|
<blockquote>
|
||||||
|
<p>You may also define this value globally: see the <a href="#environment_variables.default_google_chrome_rpcc_buffer_size">environment variables</a> section.</p>
|
||||||
|
</blockquote>
|
||||||
|
|
||||||
|
<h3 class="Heading"><a class="Anchor" aria-hidden="true" id="html.rpcc_buffer_size.c_url" href="#html.rpcc_buffer_size.c_url">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>
|
||||||
|
</a>cURL</h3>
|
||||||
|
|
||||||
|
<pre class="chroma">$ curl --request POST <span class="se">\
|
||||||
|
</span><span class="se"></span> --url http://localhost:3000/convert/html <span class="se">\
|
||||||
|
</span><span class="se"></span> --header <span class="s1">'Content-Type: multipart/form-data'</span> <span class="se">\
|
||||||
|
</span><span class="se"></span> --form <span class="nv">files</span><span class="o">=</span>@index.html <span class="se">\
|
||||||
|
</span><span class="se"></span> --form <span class="nv">googleChromeRpccBufferSize</span><span class="o">=</span><span class="m">1048576</span> <span class="se">\
|
||||||
|
</span><span class="se"></span> -o result.pdf
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h3 class="Heading"><a class="Anchor" aria-hidden="true" id="html.rpcc_buffer_size.go" href="#html.rpcc_buffer_size.go">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>
|
||||||
|
</a>Go</h3>
|
||||||
|
|
||||||
|
<pre class="chroma"><span class="kn">import</span> <span class="s">"github.com/thecodingmachine/gotenberg-go-client/v6"</span>
|
||||||
|
|
||||||
|
<span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
|
||||||
|
<span class="nx">c</span> <span class="o">:=</span> <span class="o">&</span><span class="nx">gotenberg</span><span class="p">.</span><span class="nx">Client</span><span class="p">{</span><span class="nx">Hostname</span><span class="p">:</span> <span class="s">"http://localhost:3000"</span><span class="p">}</span>
|
||||||
|
<span class="nx">req</span><span class="p">,</span> <span class="nx">_</span> <span class="o">:=</span> <span class="nx">gotenberg</span><span class="p">.</span><span class="nf">NewHTMLRequest</span><span class="p">(</span><span class="s">"index.html"</span><span class="p">)</span>
|
||||||
|
<span class="nx">req</span><span class="p">.</span><span class="nf">GoogleChromeRpccBufferSize</span><span class="p">(</span><span class="mi">1048576</span><span class="p">)</span>
|
||||||
|
<span class="nx">dest</span> <span class="o">:=</span> <span class="s">"result.pdf"</span>
|
||||||
|
<span class="nx">c</span><span class="p">.</span><span class="nf">Store</span><span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">dest</span><span class="p">)</span>
|
||||||
|
<span class="p">}</span>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h3 class="Heading"><a class="Anchor" aria-hidden="true" id="html.rpcc_buffer_size.php" href="#html.rpcc_buffer_size.php">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>
|
||||||
|
</a>PHP</h3>
|
||||||
|
|
||||||
|
<pre class="chroma">use TheCodingMachine\Gotenberg\Client;
|
||||||
|
use TheCodingMachine\Gotenberg\DocumentFactory;
|
||||||
|
use TheCodingMachine\Gotenberg\HTMLRequest;
|
||||||
|
use TheCodingMachine\Gotenberg\Request;
|
||||||
|
|
||||||
|
$client = new Client('http://localhost:3000', new \Http\Adapter\Guzzle6\Client());
|
||||||
|
$index = DocumentFactory::makeFromPath('index.html', 'index.html');
|
||||||
|
$request = new HTMLRequest($index);
|
||||||
|
$request->setGoogleChromeRpccBufferSize(1048576);
|
||||||
|
$dest = "result.pdf";
|
||||||
|
$client->store($request, $dest);
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -1328,7 +1405,7 @@ Indeed, Google Chrome misbehaves if there are too many concurrent conversions.</
|
|||||||
<p><strong>The more concurrent requests, the more <code>504</code> HTTP codes the API will return.</strong></p>
|
<p><strong>The more concurrent requests, the more <code>504</code> HTTP codes the API will return.</strong></p>
|
||||||
|
|
||||||
<blockquote>
|
<blockquote>
|
||||||
<p>See our <a href="../loadtesting">load testing use case</a> for more details about the API behaviour under heavy load.</p>
|
<p>See our <a href="https://github.com/thecodingmachine/gotenberg/tree/master/loadtesting">load testing use case</a> for more details about the API behaviour under heavy load.</p>
|
||||||
</blockquote>
|
</blockquote>
|
||||||
|
|
||||||
<h2 class="Heading"><a class="Anchor" aria-hidden="true" id="scalability.strategies" href="#scalability.strategies">
|
<h2 class="Heading"><a class="Anchor" aria-hidden="true" id="scalability.strategies" href="#scalability.strategies">
|
||||||
@@ -1399,7 +1476,11 @@ restart your Gotenberg instances from time to time to ensure a nominal behaviour
|
|||||||
|
|
||||||
<pre class="chroma"><span class="k">FROM</span><span class="s"> thecodingmachine/gotenberg:6</span><span class="err">
|
<pre class="chroma"><span class="k">FROM</span><span class="s"> thecodingmachine/gotenberg:6</span><span class="err">
|
||||||
</span><span class="err">
|
</span><span class="err">
|
||||||
|
</span><span class="err"></span><span class="k">USER</span><span class="s"> root</span><span class="err">
|
||||||
|
</span><span class="err">
|
||||||
</span><span class="err"></span><span class="k">RUN</span> apt-get -y install yourfonts<span class="err">
|
</span><span class="err"></span><span class="k">RUN</span> apt-get -y install yourfonts<span class="err">
|
||||||
|
</span><span class="err">
|
||||||
|
</span><span class="err"></span><span class="k">USER</span><span class="s"> gotenberg</span><span class="err">
|
||||||
</span></pre>
|
</span></pre>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"github.com/thecodingmachine/gotenberg/internal/app/xhttp/pkg/context"
|
"github.com/thecodingmachine/gotenberg/internal/app/xhttp/pkg/context"
|
||||||
"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/printer"
|
"github.com/thecodingmachine/gotenberg/internal/pkg/printer"
|
||||||
"github.com/thecodingmachine/gotenberg/internal/pkg/xerror"
|
"github.com/thecodingmachine/gotenberg/internal/pkg/xerror"
|
||||||
"github.com/thecodingmachine/gotenberg/internal/pkg/xrand"
|
"github.com/thecodingmachine/gotenberg/internal/pkg/xrand"
|
||||||
@@ -24,6 +25,31 @@ const (
|
|||||||
officeEndpoint string = "/office"
|
officeEndpoint string = "/office"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func isMultipartFormDataEndpoint(config conf.Config, path string) bool {
|
||||||
|
var multipartFormDataEndpoints []string
|
||||||
|
multipartFormDataEndpoints = append(multipartFormDataEndpoints, mergeEndpoint)
|
||||||
|
if !config.DisableGoogleChrome() {
|
||||||
|
multipartFormDataEndpoints = append(
|
||||||
|
multipartFormDataEndpoints,
|
||||||
|
fmt.Sprintf("%s%s", convertGroupEndpoint, htmlEndpoint),
|
||||||
|
fmt.Sprintf("%s%s", convertGroupEndpoint, urlEndpoint),
|
||||||
|
fmt.Sprintf("%s%s", convertGroupEndpoint, markdownEndpoint),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if !config.DisableUnoconv() {
|
||||||
|
multipartFormDataEndpoints = append(
|
||||||
|
multipartFormDataEndpoints,
|
||||||
|
fmt.Sprintf("%s%s", convertGroupEndpoint, officeEndpoint),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
for _, endpoint := range multipartFormDataEndpoints {
|
||||||
|
if endpoint == path {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// pingHandler is the handler for healthcheck.
|
// pingHandler is the handler for healthcheck.
|
||||||
func pingHandler(c echo.Context) error {
|
func pingHandler(c echo.Context) error {
|
||||||
const op string = "xhttp.pingHandler"
|
const op string = "xhttp.pingHandler"
|
||||||
|
|||||||
@@ -19,9 +19,11 @@ func TestPingHandler(t *testing.T) {
|
|||||||
// should return 200.
|
// should return 200.
|
||||||
config := conf.DefaultConfig()
|
config := conf.DefaultConfig()
|
||||||
srv := New(config)
|
srv := New(config)
|
||||||
srv = New(config)
|
|
||||||
req := httptest.NewRequest(http.MethodGet, pingEndpoint, nil)
|
req := httptest.NewRequest(http.MethodGet, pingEndpoint, nil)
|
||||||
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
test.AssertStatusCode(t, http.StatusOK, srv, req)
|
||||||
|
// should return 405 as Method is wrong.
|
||||||
|
req = httptest.NewRequest(http.MethodPost, pingEndpoint, nil)
|
||||||
|
test.AssertStatusCode(t, http.StatusMethodNotAllowed, srv, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMergeHandler(t *testing.T) {
|
func TestMergeHandler(t *testing.T) {
|
||||||
@@ -32,6 +34,13 @@ func TestMergeHandler(t *testing.T) {
|
|||||||
req := httptest.NewRequest(http.MethodPost, mergeEndpoint, body)
|
req := httptest.NewRequest(http.MethodPost, mergeEndpoint, 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.
|
||||||
|
req = httptest.NewRequest(http.MethodGet, mergeEndpoint, nil)
|
||||||
|
test.AssertStatusCode(t, http.StatusMethodNotAllowed, srv, req)
|
||||||
|
// should return 415 as Content-Type is wrong.
|
||||||
|
body, _ = test.MergeMultipartForm(t, nil)
|
||||||
|
req = httptest.NewRequest(http.MethodPost, mergeEndpoint, body)
|
||||||
|
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"})
|
||||||
@@ -66,6 +75,13 @@ func TestHTMLHandler(t *testing.T) {
|
|||||||
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)
|
||||||
|
// should return 405 as Method is wrong.
|
||||||
|
req = httptest.NewRequest(http.MethodGet, endpoint, nil)
|
||||||
|
test.AssertStatusCode(t, http.StatusMethodNotAllowed, srv, req)
|
||||||
|
// should return 415 as Content-Type is wrong.
|
||||||
|
body, _ = test.HTMLMultipartForm(t, nil)
|
||||||
|
req = httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||||
|
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.HTMLMultipartForm(t, map[string]string{string(resource.WaitTimeoutArgKey): "-1"})
|
body, contentType = test.HTMLMultipartForm(t, map[string]string{string(resource.WaitTimeoutArgKey): "-1"})
|
||||||
@@ -185,6 +201,24 @@ func TestHTMLHandler(t *testing.T) {
|
|||||||
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.StatusBadRequest, srv, req)
|
test.AssertStatusCode(t, http.StatusBadRequest, srv, req)
|
||||||
|
// should return 400 as "googleChromeRpccBufferSize" form field
|
||||||
|
// value is < 0.
|
||||||
|
body, contentType = test.HTMLMultipartForm(t, map[string]string{string(resource.GoogleChromeRpccBufferSizeArgKey): "-1"})
|
||||||
|
req = httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||||
|
req.Header.Set(echo.HeaderContentType, contentType)
|
||||||
|
test.AssertStatusCode(t, http.StatusBadRequest, srv, req)
|
||||||
|
// should return 400 as "googleChromeRpccBufferSize" form field
|
||||||
|
// value is is > config.MaximumGoogleChromeRpccBufferSize().
|
||||||
|
body, contentType = test.HTMLMultipartForm(t, map[string]string{string(resource.GoogleChromeRpccBufferSizeArgKey): "104857601"})
|
||||||
|
req = httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||||
|
req.Header.Set(echo.HeaderContentType, contentType)
|
||||||
|
test.AssertStatusCode(t, http.StatusBadRequest, srv, req)
|
||||||
|
// should return 400 as "googleChromeRpccBufferSize" form field
|
||||||
|
// value is invalid.
|
||||||
|
body, contentType = test.HTMLMultipartForm(t, map[string]string{string(resource.GoogleChromeRpccBufferSizeArgKey): "not an int"})
|
||||||
|
req = httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||||
|
req.Header.Set(echo.HeaderContentType, contentType)
|
||||||
|
test.AssertStatusCode(t, http.StatusBadRequest, srv, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestURLHandler(t *testing.T) {
|
func TestURLHandler(t *testing.T) {
|
||||||
@@ -196,6 +230,13 @@ func TestURLHandler(t *testing.T) {
|
|||||||
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)
|
||||||
|
// should return 405 as Method is wrong.
|
||||||
|
req = httptest.NewRequest(http.MethodGet, endpoint, nil)
|
||||||
|
test.AssertStatusCode(t, http.StatusMethodNotAllowed, srv, req)
|
||||||
|
// should return 415 as Content-Type is wrong.
|
||||||
|
body, _ = test.URLMultipartForm(t, nil)
|
||||||
|
req = httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||||
|
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.URLMultipartForm(t, map[string]string{string(resource.WaitTimeoutArgKey): "-1"})
|
body, contentType = test.URLMultipartForm(t, map[string]string{string(resource.WaitTimeoutArgKey): "-1"})
|
||||||
@@ -315,6 +356,24 @@ func TestURLHandler(t *testing.T) {
|
|||||||
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.StatusBadRequest, srv, req)
|
test.AssertStatusCode(t, http.StatusBadRequest, srv, req)
|
||||||
|
// should return 400 as "googleChromeRpccBufferSize" form field
|
||||||
|
// value is < 0.
|
||||||
|
body, contentType = test.URLMultipartForm(t, map[string]string{string(resource.GoogleChromeRpccBufferSizeArgKey): "-1"})
|
||||||
|
req = httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||||
|
req.Header.Set(echo.HeaderContentType, contentType)
|
||||||
|
test.AssertStatusCode(t, http.StatusBadRequest, srv, req)
|
||||||
|
// should return 400 as "googleChromeRpccBufferSize" form field
|
||||||
|
// value is is > config.MaximumGoogleChromeRpccBufferSize().
|
||||||
|
body, contentType = test.URLMultipartForm(t, map[string]string{string(resource.GoogleChromeRpccBufferSizeArgKey): "104857601"})
|
||||||
|
req = httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||||
|
req.Header.Set(echo.HeaderContentType, contentType)
|
||||||
|
test.AssertStatusCode(t, http.StatusBadRequest, srv, req)
|
||||||
|
// should return 400 as "googleChromeRpccBufferSize" form field
|
||||||
|
// value is invalid.
|
||||||
|
body, contentType = test.URLMultipartForm(t, map[string]string{string(resource.GoogleChromeRpccBufferSizeArgKey): "not an int"})
|
||||||
|
req = httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||||
|
req.Header.Set(echo.HeaderContentType, contentType)
|
||||||
|
test.AssertStatusCode(t, http.StatusBadRequest, srv, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMarkdownHandler(t *testing.T) {
|
func TestMarkdownHandler(t *testing.T) {
|
||||||
@@ -326,6 +385,13 @@ func TestMarkdownHandler(t *testing.T) {
|
|||||||
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)
|
||||||
|
// should return 405 as Method is wrong.
|
||||||
|
req = httptest.NewRequest(http.MethodGet, endpoint, nil)
|
||||||
|
test.AssertStatusCode(t, http.StatusMethodNotAllowed, srv, req)
|
||||||
|
// should return 415 as Content-Type is wrong.
|
||||||
|
body, _ = test.MarkdownMultipartForm(t, nil)
|
||||||
|
req = httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||||
|
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.MarkdownMultipartForm(t, map[string]string{string(resource.WaitTimeoutArgKey): "-1"})
|
body, contentType = test.MarkdownMultipartForm(t, map[string]string{string(resource.WaitTimeoutArgKey): "-1"})
|
||||||
@@ -445,6 +511,24 @@ func TestMarkdownHandler(t *testing.T) {
|
|||||||
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.StatusBadRequest, srv, req)
|
test.AssertStatusCode(t, http.StatusBadRequest, srv, req)
|
||||||
|
// should return 400 as "googleChromeRpccBufferSize" form field
|
||||||
|
// value is < 0.
|
||||||
|
body, contentType = test.MarkdownMultipartForm(t, map[string]string{string(resource.GoogleChromeRpccBufferSizeArgKey): "-1"})
|
||||||
|
req = httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||||
|
req.Header.Set(echo.HeaderContentType, contentType)
|
||||||
|
test.AssertStatusCode(t, http.StatusBadRequest, srv, req)
|
||||||
|
// should return 400 as "googleChromeRpccBufferSize" form field
|
||||||
|
// value is is > config.MaximumGoogleChromeRpccBufferSize().
|
||||||
|
body, contentType = test.MarkdownMultipartForm(t, map[string]string{string(resource.GoogleChromeRpccBufferSizeArgKey): "104857601"})
|
||||||
|
req = httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||||
|
req.Header.Set(echo.HeaderContentType, contentType)
|
||||||
|
test.AssertStatusCode(t, http.StatusBadRequest, srv, req)
|
||||||
|
// should return 400 as "googleChromeRpccBufferSize" form field
|
||||||
|
// value is invalid.
|
||||||
|
body, contentType = test.MarkdownMultipartForm(t, map[string]string{string(resource.GoogleChromeRpccBufferSizeArgKey): "not an int"})
|
||||||
|
req = httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||||
|
req.Header.Set(echo.HeaderContentType, contentType)
|
||||||
|
test.AssertStatusCode(t, http.StatusBadRequest, srv, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOfficeHandler(t *testing.T) {
|
func TestOfficeHandler(t *testing.T) {
|
||||||
@@ -456,6 +540,13 @@ func TestOfficeHandler(t *testing.T) {
|
|||||||
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)
|
||||||
|
// should return 405 as Method is wrong.
|
||||||
|
req = httptest.NewRequest(http.MethodGet, endpoint, nil)
|
||||||
|
test.AssertStatusCode(t, http.StatusMethodNotAllowed, srv, req)
|
||||||
|
// should return 415 as Content-Type is wrong.
|
||||||
|
body, _ = test.OfficeMultipartForm(t, nil)
|
||||||
|
req = httptest.NewRequest(http.MethodPost, endpoint, body)
|
||||||
|
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.OfficeMultipartForm(t, map[string]string{string(resource.WaitTimeoutArgKey): "-1"})
|
body, contentType = test.OfficeMultipartForm(t, map[string]string{string(resource.WaitTimeoutArgKey): "-1"})
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package xhttp
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"github.com/thecodingmachine/gotenberg/internal/app/xhttp/pkg/context"
|
"github.com/thecodingmachine/gotenberg/internal/app/xhttp/pkg/context"
|
||||||
@@ -25,12 +26,28 @@ func contextMiddleware(config conf.Config) echo.MiddlewareFunc {
|
|||||||
// extend the current echo context with our custom
|
// extend the current echo context with our custom
|
||||||
// context.
|
// context.
|
||||||
ctx := context.New(c, logger, config)
|
ctx := context.New(c, logger, config)
|
||||||
// if its an healthcheck request, there
|
// if it's not a multipart/form-data request,
|
||||||
// is no need to create a Resource.
|
// there is no need to create a Resource.
|
||||||
if ctx.Path() == pingEndpoint {
|
if !isMultipartFormDataEndpoint(config, ctx.Path()) {
|
||||||
|
// validate method for healthcheck endpoint.
|
||||||
|
if ctx.Path() == pingEndpoint && ctx.Request().Method != http.MethodGet {
|
||||||
|
err := doErr(ctx, echo.NewHTTPError(http.StatusMethodNotAllowed))
|
||||||
|
return ctx.LogRequestResult(err, false)
|
||||||
|
}
|
||||||
return next(ctx)
|
return next(ctx)
|
||||||
}
|
}
|
||||||
// if the endpoint is not for healthcheck, create a
|
// validate method.
|
||||||
|
if ctx.Request().Method != http.MethodPost {
|
||||||
|
err := doErr(ctx, echo.NewHTTPError(http.StatusMethodNotAllowed))
|
||||||
|
return ctx.LogRequestResult(err, false)
|
||||||
|
}
|
||||||
|
// validate Content-Type.
|
||||||
|
contentType := ctx.Request().Header.Get("Content-Type")
|
||||||
|
if !strings.Contains(contentType, "multipart/form-data") {
|
||||||
|
err := doErr(ctx, echo.NewHTTPError(http.StatusUnsupportedMediaType))
|
||||||
|
return ctx.LogRequestResult(err, false)
|
||||||
|
}
|
||||||
|
// it's a multipart/form-data request, create a
|
||||||
// Resource.
|
// Resource.
|
||||||
if err := ctx.WithResource(trace); err != nil {
|
if err := ctx.WithResource(trace); err != nil {
|
||||||
err = doCleanup(ctx, err)
|
err = doCleanup(ctx, err)
|
||||||
|
|||||||
@@ -48,18 +48,23 @@ func chromePrinterOptions(r resource.Resource, config conf.Config) (printer.Chro
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return printer.ChromePrinterOptions{}, err
|
return printer.ChromePrinterOptions{}, err
|
||||||
}
|
}
|
||||||
|
googleChromeRpccBufferSize, err := resource.GoogleChromeRpccBufferSizeArg(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,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
opts, err := resolver()
|
opts, err := resolver()
|
||||||
|
|||||||
@@ -51,6 +51,9 @@ const (
|
|||||||
// LandscapeArgKey is the key
|
// LandscapeArgKey is the key
|
||||||
// of the argument "landscape".
|
// of the argument "landscape".
|
||||||
LandscapeArgKey ArgKey = "landscape"
|
LandscapeArgKey ArgKey = "landscape"
|
||||||
|
// GoogleChromeRpccBufferSizeArgKey is the key
|
||||||
|
// of the argument "googleChromeRpccBufferSize".
|
||||||
|
GoogleChromeRpccBufferSizeArgKey ArgKey = "googleChromeRpccBufferSize"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -73,6 +76,7 @@ func ArgKeys() []ArgKey {
|
|||||||
MarginLeftArgKey,
|
MarginLeftArgKey,
|
||||||
MarginRightArgKey,
|
MarginRightArgKey,
|
||||||
LandscapeArgKey,
|
LandscapeArgKey,
|
||||||
|
GoogleChromeRpccBufferSizeArgKey,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,3 +269,24 @@ func MarginArgs(r Resource, config conf.Config) (float64, float64, float64, floa
|
|||||||
marginRight,
|
marginRight,
|
||||||
nil
|
nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
GoogleChromeRpccBufferSizeArg is a helper for retrieving
|
||||||
|
the "googleChromeRpccBufferSize" argument as int64.
|
||||||
|
|
||||||
|
It also validates it against the application
|
||||||
|
configuration.
|
||||||
|
*/
|
||||||
|
func GoogleChromeRpccBufferSizeArg(r Resource, config conf.Config) (int64, error) {
|
||||||
|
const op string = "resource.GoogleChromeRpccBufferSizeArg"
|
||||||
|
result, err := r.Int64Arg(
|
||||||
|
GoogleChromeRpccBufferSizeArgKey,
|
||||||
|
config.DefaultGoogleChromeRpccBufferSize(),
|
||||||
|
xassert.Int64NotInferiorTo(0.0),
|
||||||
|
xassert.Int64NotSuperiorTo(config.MaximumGoogleChromeRpccBufferSize()),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return result, xerror.New(op, err)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ func TestArgKeys(t *testing.T) {
|
|||||||
MarginLeftArgKey,
|
MarginLeftArgKey,
|
||||||
MarginRightArgKey,
|
MarginRightArgKey,
|
||||||
LandscapeArgKey,
|
LandscapeArgKey,
|
||||||
|
GoogleChromeRpccBufferSizeArgKey,
|
||||||
}
|
}
|
||||||
assert.Equal(t, expected, ArgKeys())
|
assert.Equal(t, expected, ArgKeys())
|
||||||
}
|
}
|
||||||
@@ -300,3 +301,47 @@ func TestMarginArgs(t *testing.T) {
|
|||||||
err = r.Close()
|
err = r.Close()
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGoogleChromeRpccBufferSizeArg(t *testing.T) {
|
||||||
|
const resourceDirectoryName string = "foo"
|
||||||
|
var expected int64
|
||||||
|
logger := test.DebugLogger()
|
||||||
|
config := conf.DefaultConfig()
|
||||||
|
r, err := New(logger, resourceDirectoryName)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
// argument does not exist.
|
||||||
|
expected = config.DefaultGoogleChromeRpccBufferSize()
|
||||||
|
v, err := GoogleChromeRpccBufferSizeArg(r, config)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, expected, v)
|
||||||
|
// argument exist.
|
||||||
|
expected = 10
|
||||||
|
r.WithArg(GoogleChromeRpccBufferSizeArgKey, "10")
|
||||||
|
v, err = GoogleChromeRpccBufferSizeArg(r, config)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, expected, v)
|
||||||
|
// should not be OK as argument
|
||||||
|
// value is < 0.
|
||||||
|
expected = config.DefaultGoogleChromeRpccBufferSize()
|
||||||
|
r.WithArg(GoogleChromeRpccBufferSizeArgKey, "-1")
|
||||||
|
v, err = GoogleChromeRpccBufferSizeArg(r, config)
|
||||||
|
test.AssertError(t, err)
|
||||||
|
assert.Equal(t, expected, v)
|
||||||
|
// should not be OK as argument
|
||||||
|
// value is > config.MaximumGoogleChromeRpccBufferSize().
|
||||||
|
expected = config.DefaultGoogleChromeRpccBufferSize()
|
||||||
|
r.WithArg(GoogleChromeRpccBufferSizeArgKey, "104857601")
|
||||||
|
v, err = GoogleChromeRpccBufferSizeArg(r, config)
|
||||||
|
test.AssertError(t, err)
|
||||||
|
assert.Equal(t, expected, v)
|
||||||
|
// should not be OK as
|
||||||
|
// argument value is invalid.
|
||||||
|
expected = config.DefaultGoogleChromeRpccBufferSize()
|
||||||
|
r.WithArg(GoogleChromeRpccBufferSizeArgKey, "foo")
|
||||||
|
v, err = GoogleChromeRpccBufferSizeArg(r, config)
|
||||||
|
test.AssertError(t, err)
|
||||||
|
assert.Equal(t, expected, v)
|
||||||
|
// finally...
|
||||||
|
err = r.Close()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ func TestHeaderFooterContents(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
defer f2.Close() // nolint: errcheck
|
defer f2.Close() // nolint: errcheck
|
||||||
err = r.WithFile("footer.html", f2)
|
err = r.WithFile("footer.html", f2)
|
||||||
|
assert.Nil(t, err)
|
||||||
header, footer, err = HeaderFooterContents(r, config)
|
header, footer, err = HeaderFooterContents(r, config)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Contains(t, header, expected)
|
assert.Contains(t, header, expected)
|
||||||
|
|||||||
@@ -261,6 +261,7 @@ func TestFcontent(t *testing.T) {
|
|||||||
err = r.WithFile(filename, f)
|
err = r.WithFile(filename, f)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
v, err = r.Fcontent(filename, defaultValue)
|
v, err = r.Fcontent(filename, defaultValue)
|
||||||
|
assert.Nil(t, err)
|
||||||
assert.Contains(t, v, defaultValue)
|
assert.Contains(t, v, defaultValue)
|
||||||
// finally...
|
// finally...
|
||||||
err = r.Close()
|
err = r.Close()
|
||||||
|
|||||||
@@ -13,6 +13,15 @@ import (
|
|||||||
"github.com/thecodingmachine/gotenberg/test"
|
"github.com/thecodingmachine/gotenberg/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestNonExistingEndpoint(t *testing.T) {
|
||||||
|
config, err := conf.FromEnv()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
srv := New(config)
|
||||||
|
// "/" endpoint should return 404.
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||||
|
test.AssertStatusCode(t, http.StatusNotFound, srv, req)
|
||||||
|
}
|
||||||
|
|
||||||
func TestDisableChromeEndpoints(t *testing.T) {
|
func TestDisableChromeEndpoints(t *testing.T) {
|
||||||
os.Setenv(conf.DisableGoogleChromeEnvVar, "1")
|
os.Setenv(conf.DisableGoogleChromeEnvVar, "1")
|
||||||
config, err := conf.FromEnv()
|
config, err := conf.FromEnv()
|
||||||
@@ -73,7 +82,7 @@ func TestDisableUnoconvEndpoints(t *testing.T) {
|
|||||||
req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, urlEndpoint), body)
|
req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, urlEndpoint), 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 404.
|
// 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, fmt.Sprintf("%s%s", convertGroupEndpoint, markdownEndpoint), body)
|
||||||
req.Header.Set(echo.HeaderContentType, contentType)
|
req.Header.Set(echo.HeaderContentType, contentType)
|
||||||
|
|||||||
@@ -34,35 +34,42 @@ 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"
|
||||||
|
// DefaultGoogleChromeRpccBufferSizeEnvVar contains the name
|
||||||
|
// of the environment variable "DEFAULT_GOOGLE_CHROME_RPCC_BUFFER_SIZE".
|
||||||
|
DefaultGoogleChromeRpccBufferSizeEnvVar string = "DEFAULT_GOOGLE_CHROME_RPCC_BUFFER_SIZE"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config contains the application
|
// Config contains the application
|
||||||
// configuration.
|
// configuration.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
maximumWaitTimeout float64
|
maximumWaitTimeout float64
|
||||||
maximumWaitDelay float64
|
maximumWaitDelay float64
|
||||||
maximumWebhookURLTimeout float64
|
maximumWebhookURLTimeout float64
|
||||||
defaultWaitTimeout float64
|
defaultWaitTimeout float64
|
||||||
defaultWebhookURLTimeout float64
|
defaultWebhookURLTimeout float64
|
||||||
defaultListenPort int64
|
defaultListenPort int64
|
||||||
disableGoogleChrome bool
|
disableGoogleChrome bool
|
||||||
disableUnoconv bool
|
disableUnoconv bool
|
||||||
logLevel xlog.Level
|
logLevel xlog.Level
|
||||||
|
maximumGoogleChromeRpccBufferSize int64
|
||||||
|
defaultGoogleChromeRpccBufferSize int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultConfig returns the default
|
// DefaultConfig returns the default
|
||||||
// configuration.
|
// configuration.
|
||||||
func DefaultConfig() Config {
|
func DefaultConfig() Config {
|
||||||
return Config{
|
return Config{
|
||||||
maximumWaitTimeout: 30.0,
|
maximumWaitTimeout: 30.0,
|
||||||
maximumWaitDelay: 10.0,
|
maximumWaitDelay: 10.0,
|
||||||
maximumWebhookURLTimeout: 30.0,
|
maximumWebhookURLTimeout: 30.0,
|
||||||
defaultWaitTimeout: 10.0,
|
defaultWaitTimeout: 10.0,
|
||||||
defaultWebhookURLTimeout: 10.0,
|
defaultWebhookURLTimeout: 10.0,
|
||||||
defaultListenPort: 3000,
|
defaultListenPort: 3000,
|
||||||
disableGoogleChrome: false,
|
disableGoogleChrome: false,
|
||||||
disableUnoconv: false,
|
disableUnoconv: false,
|
||||||
logLevel: xlog.InfoLevel,
|
logLevel: xlog.InfoLevel,
|
||||||
|
maximumGoogleChromeRpccBufferSize: 104857600, // ~100 MB
|
||||||
|
defaultGoogleChromeRpccBufferSize: 1048576, // 1 MB
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,6 +163,16 @@ func FromEnv() (Config, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return c, err
|
return c, err
|
||||||
}
|
}
|
||||||
|
defaultGoogleChromeRpccBufferSize, err := xassert.Int64FromEnv(
|
||||||
|
DefaultGoogleChromeRpccBufferSizeEnvVar,
|
||||||
|
c.defaultGoogleChromeRpccBufferSize,
|
||||||
|
xassert.Int64NotInferiorTo(0),
|
||||||
|
xassert.Int64NotSuperiorTo(c.MaximumGoogleChromeRpccBufferSize()),
|
||||||
|
)
|
||||||
|
c.defaultGoogleChromeRpccBufferSize = defaultGoogleChromeRpccBufferSize
|
||||||
|
if err != nil {
|
||||||
|
return c, err
|
||||||
|
}
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
result, err := resolver()
|
result, err := resolver()
|
||||||
@@ -224,3 +241,15 @@ func (c Config) DisableUnoconv() bool {
|
|||||||
func (c Config) LogLevel() xlog.Level {
|
func (c Config) LogLevel() xlog.Level {
|
||||||
return c.logLevel
|
return c.logLevel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MaximumGoogleChromeRpccBufferSize returns the maximum
|
||||||
|
// Google Chrome rpcc buffer size from the configuration.
|
||||||
|
func (c Config) MaximumGoogleChromeRpccBufferSize() int64 {
|
||||||
|
return c.maximumGoogleChromeRpccBufferSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultGoogleChromeRpccBufferSize returns the default
|
||||||
|
// Google Chrome rpcc buffer size from the configuration.
|
||||||
|
func (c Config) DefaultGoogleChromeRpccBufferSize() int64 {
|
||||||
|
return c.defaultGoogleChromeRpccBufferSize
|
||||||
|
}
|
||||||
|
|||||||
@@ -320,6 +320,43 @@ func TestLogLevelFromEnv(t *testing.T) {
|
|||||||
os.Unsetenv(LogLevelEnvVar)
|
os.Unsetenv(LogLevelEnvVar)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDefaultGoogleChromeRpccBufferSizeFromEnv(t *testing.T) {
|
||||||
|
var (
|
||||||
|
expected Config
|
||||||
|
result Config
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
// DEFAULT_GOOGLE_CHROME_RPCC_BUFFER_SIZE correctly set.
|
||||||
|
os.Setenv(DefaultGoogleChromeRpccBufferSizeEnvVar, "100")
|
||||||
|
expected = DefaultConfig()
|
||||||
|
expected.defaultGoogleChromeRpccBufferSize = 100
|
||||||
|
result, err = FromEnv()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, expected, result)
|
||||||
|
os.Unsetenv(DefaultGoogleChromeRpccBufferSizeEnvVar)
|
||||||
|
// DEFAULT_GOOGLE_CHROME_RPCC_BUFFER_SIZE wrongly set.
|
||||||
|
os.Setenv(DefaultGoogleChromeRpccBufferSizeEnvVar, "foo")
|
||||||
|
expected = DefaultConfig()
|
||||||
|
result, err = FromEnv()
|
||||||
|
test.AssertError(t, err)
|
||||||
|
assert.Equal(t, expected, result)
|
||||||
|
os.Unsetenv(DefaultGoogleChromeRpccBufferSizeEnvVar)
|
||||||
|
// DEFAULT_GOOGLE_CHROME_RPCC_BUFFER_SIZE < 0.
|
||||||
|
os.Setenv(DefaultGoogleChromeRpccBufferSizeEnvVar, "-1")
|
||||||
|
expected = DefaultConfig()
|
||||||
|
result, err = FromEnv()
|
||||||
|
test.AssertError(t, err)
|
||||||
|
assert.Equal(t, expected, result)
|
||||||
|
os.Unsetenv(DefaultGoogleChromeRpccBufferSizeEnvVar)
|
||||||
|
// DEFAULT_GOOGLE_CHROME_RPCC_BUFFER_SIZE > 100 MB (maximumGoogleChromeRpccBufferSize).
|
||||||
|
os.Setenv(DefaultGoogleChromeRpccBufferSizeEnvVar, "104857601")
|
||||||
|
expected = DefaultConfig()
|
||||||
|
result, err = FromEnv()
|
||||||
|
test.AssertError(t, err)
|
||||||
|
assert.Equal(t, expected, result)
|
||||||
|
os.Unsetenv(DefaultGoogleChromeRpccBufferSizeEnvVar)
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetters(t *testing.T) {
|
func TestGetters(t *testing.T) {
|
||||||
result := DefaultConfig()
|
result := DefaultConfig()
|
||||||
assert.Equal(t, result.maximumWaitTimeout, result.MaximumWaitTimeout())
|
assert.Equal(t, result.maximumWaitTimeout, result.MaximumWaitTimeout())
|
||||||
@@ -331,4 +368,6 @@ 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.maximumGoogleChromeRpccBufferSize, result.MaximumGoogleChromeRpccBufferSize())
|
||||||
|
assert.Equal(t, result.defaultGoogleChromeRpccBufferSize, result.DefaultGoogleChromeRpccBufferSize())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/mafredri/cdp"
|
"github.com/mafredri/cdp"
|
||||||
@@ -29,17 +30,18 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultChromePrinterOptions returns the default
|
// DefaultChromePrinterOptions returns the default
|
||||||
@@ -47,17 +49,18 @@ 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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +113,18 @@ func (p chromePrinter) Print(destination string) error {
|
|||||||
}
|
}
|
||||||
// connect the client to the new target.
|
// connect the client to the new target.
|
||||||
newTargetWsURL := fmt.Sprintf("ws://127.0.0.1:9222/devtools/page/%s", newTarget.TargetID)
|
newTargetWsURL := fmt.Sprintf("ws://127.0.0.1:9222/devtools/page/%s", newTarget.TargetID)
|
||||||
newContextConn, err := rpcc.DialContext(ctx, newTargetWsURL)
|
newContextConn, err := rpcc.DialContext(
|
||||||
|
ctx,
|
||||||
|
newTargetWsURL,
|
||||||
|
/*
|
||||||
|
see:
|
||||||
|
https://github.com/thecodingmachine/gotenberg/issues/108
|
||||||
|
https://github.com/mafredri/cdp/issues/4
|
||||||
|
https://github.com/ChromeDevTools/devtools-protocol/issues/24
|
||||||
|
*/
|
||||||
|
rpcc.WithWriteBufferSize(int(p.opts.RpccBufferSize)),
|
||||||
|
rpcc.WithCompression(),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -159,6 +173,16 @@ func (p chromePrinter) Print(destination string) error {
|
|||||||
SetPrintBackground(true),
|
SetPrintBackground(true),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if strings.Contains(err.Error(), "rpcc: message too large") {
|
||||||
|
return xerror.Invalid(
|
||||||
|
op,
|
||||||
|
fmt.Sprintf(
|
||||||
|
"'%d' bytes are not enough: increase the Google Chrome rpcc buffer size (up to 100 MB)",
|
||||||
|
p.opts.RpccBufferSize,
|
||||||
|
),
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := ioutil.WriteFile(destination, print.Data, 0644); err != nil {
|
if err := ioutil.WriteFile(destination, print.Data, 0644); err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user