Compare commits

..

8 Commits
6.0.0 ... 6.0.2

Author SHA1 Message Date
Julien Neuhart
f90442cfb1 Merge pull request #124 from thecodingmachine/wrong_http_error_codes
improving HTTP error code and fixing #67
2019-10-08 11:05:29 +02:00
Julien Neuhart
2a44c0f65a improving HTTP error code and fixing #67 2019-10-08 10:50:15 +02:00
Julien Neuhart
cd8d2b5e95 Merge pull request #121 from thecodingmachine/rpcc_message_too_large
PrintToPDF: rpcc: message too large
2019-10-07 18:07:09 +02:00
Julien Neuhart
8d89b2e80a rpcc buffer size may now be set from env var or form field 2019-10-07 15:31:05 +02:00
Julien Neuhart
ab475d65ee fixing PrintToPDF: rpcc: message too large 2019-10-04 16:19:53 +02:00
Julien Neuhart
fa9963a737 fixing load testing link in documentation 2019-10-04 14:11:32 +02:00
Julien Neuhart
d65d9c9a4c fixing ineffassign issues 2019-10-03 11:59:48 +02:00
Julien Neuhart
6f8c7a613f improving documentation about header/footer, fonts and fixing a typo about paper size (fixes #118, closes #119) 2019-10-03 11:28:04 +02:00
18 changed files with 542 additions and 78 deletions

View File

@@ -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:

View File

@@ -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`.

View File

@@ -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);
```

View File

@@ -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

View File

@@ -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
``` ```

View File

@@ -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>&#34;1048576&#34;</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">&lt;</span><span class="nt">img</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAAUA AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO 9TXL0Y4OHwAAAABJRU5ErkJggg==&#34;</span> <span class="na">alt</span><span class="o">=</span><span class="s">&#34;Red dot&#34;</span> <span class="p">/&gt;</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>&lt;img src=&#34;data:image/png;base64, iVBORw0K... /&gt;</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-&gt;store($request, $dest);
</span><span class="se"></span> --header <span class="s1">&#39;Content-Type: multipart/form-data&#39;</span> <span class="se">\ </span><span class="se"></span> --header <span class="s1">&#39;Content-Type: multipart/form-data&#39;</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-&gt;setWaitDelay(5.5); $request-&gt;setWaitDelay(5.5);
$dest = &#34;result.pdf&#34;; $dest = &#34;result.pdf&#34;;
$client-&gt;store($request, $dest); $client-&gt;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">&#39;Content-Type: multipart/form-data&#39;</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">&#34;github.com/thecodingmachine/gotenberg-go-client/v6&#34;</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">&amp;</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">&#34;http://localhost:3000&#34;</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">&#34;index.html&#34;</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">&#34;result.pdf&#34;</span>
<span class="nx">c</span><span class="p">.</span><span class="nf">Store</span><span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">dest</span><span class="p">)</span>
<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(&#39;http://localhost:3000&#39;, new \Http\Adapter\Guzzle6\Client());
$index = DocumentFactory::makeFromPath(&#39;index.html&#39;, &#39;index.html&#39;);
$request = new HTMLRequest($index);
$request-&gt;setGoogleChromeRpccBufferSize(1048576);
$dest = &#34;result.pdf&#34;;
$client-&gt;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>

View File

@@ -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"

View File

@@ -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"})

View File

@@ -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)

View File

@@ -48,6 +48,10 @@ 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,
@@ -60,6 +64,7 @@ func chromePrinterOptions(r resource.Resource, config conf.Config) (printer.Chro
MarginLeft: marginLeft, MarginLeft: marginLeft,
MarginRight: marginRight, MarginRight: marginRight,
Landscape: landscape, Landscape: landscape,
RpccBufferSize: googleChromeRpccBufferSize,
}, nil }, nil
} }
opts, err := resolver() opts, err := resolver()

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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()

View File

@@ -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)

View File

@@ -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"
// 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
@@ -48,6 +51,8 @@ type Config struct {
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
@@ -63,6 +68,8 @@ func DefaultConfig() Config {
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
}

View File

@@ -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())
} }

View File

@@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"strings"
"time" "time"
"github.com/mafredri/cdp" "github.com/mafredri/cdp"
@@ -40,6 +41,7 @@ type ChromePrinterOptions struct {
MarginLeft float64 MarginLeft float64
MarginRight float64 MarginRight float64
Landscape bool Landscape bool
RpccBufferSize int64
} }
// DefaultChromePrinterOptions returns the default // DefaultChromePrinterOptions returns the default
@@ -58,6 +60,7 @@ func DefaultChromePrinterOptions(config conf.Config) ChromePrinterOptions {
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 {