Merge pull request #153 from thecodingmachine/custom_headers

Custom headers
This commit is contained in:
Julien Neuhart
2019-12-09 17:55:14 +01:00
committed by GitHub
19 changed files with 503 additions and 90 deletions

View File

@@ -18,7 +18,7 @@ Unless your project already has a PSR7 `HttpClient`, install `php-http/guzzle6-a
$ composer require php-http/guzzle6-adapter $ composer require php-http/guzzle6-adapter
``` ```
Then the PHP client: Then the [PHP client](https://github.com/thecodingmachine/gotenberg-php-client):
```bash ```bash
$ composer require thecodingmachine/gotenberg-php-client $ composer require thecodingmachine/gotenberg-php-client

View File

@@ -59,7 +59,7 @@ use TheCodingMachine\Gotenberg\HTMLRequest;
$client = new Client('http://localhost:3000', new \Http\Adapter\Guzzle6\Client()); $client = new Client('http://localhost:3000', new \Http\Adapter\Guzzle6\Client());
$index = DocumentFactory::makeFromPath('index.html', 'index.html'); $index = DocumentFactory::makeFromPath('index.html', 'index.html');
$request = new HTMLRequest($index); $request = new HTMLRequest($index);
$dest = "result.pdf"; $dest = 'result.pdf';
$client->store($request, $dest); $client->store($request, $dest);
``` ```
@@ -146,7 +146,7 @@ $footer = DocumentFactory::makeFromPath('footer.html', 'footer.html');
$request = new HTMLRequest($index); $request = new HTMLRequest($index);
$request->setHeader($header); $request->setHeader($header);
$request->setFooter($footer); $request->setFooter($footer);
$dest = "result.pdf"; $dest = 'result.pdf';
$client->store($request, $dest); $client->store($request, $dest);
``` ```
@@ -237,7 +237,7 @@ $assets = [
]; ];
$request = new HTMLRequest($index); $request = new HTMLRequest($index);
$request->setAssets($assets); $request->setAssets($assets);
$dest = "result.pdf"; $dest = 'result.pdf';
$client->store($request, $dest); $client->store($request, $dest);
``` ```
@@ -296,7 +296,7 @@ $request = new HTMLRequest($index);
$request->setPaperSize(Request::A4); $request->setPaperSize(Request::A4);
$request->setMargins(Request::NO_MARGINS); $request->setMargins(Request::NO_MARGINS);
$request->setLandscape(true); $request->setLandscape(true);
$dest = "result.pdf"; $dest = 'result.pdf';
$client->store($request, $dest); $client->store($request, $dest);
``` ```
@@ -345,7 +345,7 @@ $client = new Client('http://localhost:3000', new \Http\Adapter\Guzzle6\Client()
$index = DocumentFactory::makeFromPath('index.html', 'index.html'); $index = DocumentFactory::makeFromPath('index.html', 'index.html');
$request = new HTMLRequest($index); $request = new HTMLRequest($index);
$request->setWaitDelay(5.5); $request->setWaitDelay(5.5);
$dest = "result.pdf"; $dest = 'result.pdf';
$client->store($request, $dest); $client->store($request, $dest);
``` ```
@@ -397,6 +397,6 @@ $client = new Client('http://localhost:3000', new \Http\Adapter\Guzzle6\Client()
$index = DocumentFactory::makeFromPath('index.html', 'index.html'); $index = DocumentFactory::makeFromPath('index.html', 'index.html');
$request = new HTMLRequest($index); $request = new HTMLRequest($index);
$request->setGoogleChromeRpccBufferSize(1048576); $request->setGoogleChromeRpccBufferSize(1048576);
$dest = "result.pdf"; $dest = 'result.pdf';
$client->store($request, $dest); $client->store($request, $dest);
``` ```

View File

@@ -51,6 +51,57 @@ use TheCodingMachine\Gotenberg\URLRequest;
$client = new Client('http://localhost:3000', new \Http\Adapter\Guzzle6\Client()); $client = new Client('http://localhost:3000', new \Http\Adapter\Guzzle6\Client());
$request = new URLRequest('https://google.com'); $request = new URLRequest('https://google.com');
$request->setMargins(Request::NO_MARGINS); $request->setMargins(Request::NO_MARGINS);
$dest = "result.pdf"; $dest = 'result.pdf';
$client->store($request, $dest);
```
## Custom HTTP headers
You may send your own HTTP headers to the `remoteURL`.
For instance, by adding the HTTP header `Gotenberg-Remoteurl-Your-Header` to your request,
the API will send a request to the `remoteURL` with the HTTP header `Your-Header`.
> **Attention:** the API uses a canonical format for the HTTP headers:
> it transforms the first
> letter and any letter following a hyphen to upper case;
> the rest are converted to lowercase. For example, the
> canonical key for `accept-encoding` is `Accept-Encoding`.
### cURL
```bash
$ curl --request POST \
--url http://localhost:3000/convert/url \
--header 'Content-Type: multipart/form-data' \
--header 'Gotenberg-Remoteurl-Your-Header: Foo' \
--form remoteURL=https://google.com \
-o result.pdf
```
### Go
```golang
import "github.com/thecodingmachine/gotenberg-go-client/v6"
func main() {
c := &gotenberg.Client{Hostname: "http://localhost:3000"}
req := gotenberg.NewURLRequest("https://google.com")
req.AddRemoteURLHTTPHeader("Your-Header", "Foo")
dest := "result.pdf"
c.Store(req, dest)
}
```
### PHP
```php
use TheCodingMachine\Gotenberg\Client;
use TheCodingMachine\Gotenberg\URLRequest;
$client = new Client('http://localhost:3000', new \Http\Adapter\Guzzle6\Client());
$request = new URLRequest('https://google.com');
$request->addRemoteURLHTTPHeader('Your-Header', 'Foo')
$dest = 'result.pdf';
$client->store($request, $dest); $client->store($request, $dest);
``` ```

View File

@@ -65,6 +65,6 @@ $markdowns = [
DocumentFactory::makeFromPath('file.md', 'file.md'), DocumentFactory::makeFromPath('file.md', 'file.md'),
]; ];
$request = new MarkdownRequest($index, $markdowns); $request = new MarkdownRequest($index, $markdowns);
$dest = "result.pdf"; $dest = 'result.pdf';
$client->store($request, $dest); $client->store($request, $dest);
``` ```

View File

@@ -64,7 +64,7 @@ $files = [
DocumentFactory::makeFromPath('document2.docx', 'document2.docx'), DocumentFactory::makeFromPath('document2.docx', 'document2.docx'),
]; ];
$request = new OfficeRequest($files); $request = new OfficeRequest($files);
$dest = "result.pdf"; $dest = 'result.pdf';
$client->store($request, $dest); $client->store($request, $dest);
``` ```
@@ -112,6 +112,6 @@ $files = [
]; ];
$request = new OfficeRequest($files); $request = new OfficeRequest($files);
$request->setLandscape(true); $request->setLandscape(true);
$dest = "result.pdf"; $dest = 'result.pdf';
$client->store($request, $dest); $client->store($request, $dest);
``` ```

View File

@@ -50,6 +50,6 @@ $files = [
DocumentFactory::makeFromPath('file2.pdf', 'file2.pdf'), DocumentFactory::makeFromPath('file2.pdf', 'file2.pdf'),
]; ];
$request = new MergeRequest($files); $request = new MergeRequest($files);
$dest = "result.pdf"; $dest = 'result.pdf';
$client->store($request, $dest); $client->store($request, $dest);
``` ```

View File

@@ -48,6 +48,6 @@ $client = new Client('http://localhost:3000', new \Http\Adapter\Guzzle6\Client()
$index = DocumentFactory::makeFromPath('index.html', 'index.html'); $index = DocumentFactory::makeFromPath('index.html', 'index.html');
$request = new HTMLRequest($index); $request = new HTMLRequest($index);
$request->setWaitTimeout(2.5); $request->setWaitTimeout(2.5);
$dest = "result.pdf"; $dest = 'result.pdf';
$client->store($request, $dest); $client->store($request, $dest);
``` ```

View File

@@ -58,9 +58,7 @@ It takes a float as value (e.g `2.5` for 2.5 seconds).
> You may also define this value globally: see the [environment variables](#environment_variables.default_webhook_url_timeout) section. > You may also define this value globally: see the [environment variables](#environment_variables.default_webhook_url_timeout) section.
### Examples ### cURL
#### cURL
```bash ```bash
$ curl --request POST \ $ curl --request POST \
@@ -71,7 +69,7 @@ $ curl --request POST \
--form webhookURLTimeout=2.5 --form webhookURLTimeout=2.5
``` ```
#### Go ### Go
```golang ```golang
import "github.com/thecodingmachine/gotenberg-go-client/v6" import "github.com/thecodingmachine/gotenberg-go-client/v6"
@@ -85,7 +83,7 @@ func main() {
} }
``` ```
#### PHP ### PHP
```php ```php
use TheCodingMachine\Gotenberg\Client; use TheCodingMachine\Gotenberg\Client;
@@ -99,3 +97,56 @@ $request->setWebhookURL('http://myapp.com/webhook/');
$request->setWebhookURLTimeout(2.5); $request->setWebhookURLTimeout(2.5);
$resp = $client->post($request); $resp = $client->post($request);
``` ```
## Custom HTTP headers
You may send your own HTTP headers to the `webhookURL`.
For instance, by adding the HTTP header `Gotenberg-Webhookurl-Your-Header` to your request,
the API will send a request to the `webhookURL` with the HTTP header `Your-Header`.
> **Attention:** the API uses a canonical format for the HTTP headers:
> it transforms the first
> letter and any letter following a hyphen to upper case;
> the rest are converted to lowercase. For example, the
> canonical key for `accept-encoding` is `Accept-Encoding`.
### cURL
```bash
$ curl --request POST \
--url http://localhost:3000/convert/html \
--header 'Content-Type: multipart/form-data' \
--header 'Gotenberg-Webhookurl-Your-Header: Foo' \
--form files=@index.html \
--form webhookURL='http://myapp.com/webhook/'
```
### Go
```golang
import "github.com/thecodingmachine/gotenberg-go-client/v6"
func main() {
c := &gotenberg.Client{Hostname: "http://localhost:3000"}
req, _ := gotenberg.NewHTMLRequest("index.html")
req.WebhookURL("http://myapp.com/webhook/")
req.AddWebhookURLHTTPHeader("Your-Header", "Foo")
resp, _ := c.Post(req)
}
```
### PHP
```php
use TheCodingMachine\Gotenberg\Client;
use TheCodingMachine\Gotenberg\DocumentFactory;
use TheCodingMachine\Gotenberg\HTMLRequest;
$client = new Client('http://localhost:3000', new \Http\Adapter\Guzzle6\Client());
$index = DocumentFactory::makeFromPath('index.html', 'index.html');
$request = new HTMLRequest($index);
$request->setWebhookURL('http://myapp.com/webhook/');
$request->addWebhookURLHTTPHeader('Your-Header', 'Foo');
$resp = $client->post($request);
```

View File

@@ -233,7 +233,7 @@ Gotenberg API is available at <a href="http://localhost:3000">http://localhost:3
<pre class="chroma">$ composer require php-http/guzzle6-adapter <pre class="chroma">$ composer require php-http/guzzle6-adapter
</pre> </pre>
<p>Then the PHP client:</p> <p>Then the <a href="https://github.com/thecodingmachine/gotenberg-php-client">PHP client</a>:</p>
<pre class="chroma">$ composer require thecodingmachine/gotenberg-php-client <pre class="chroma">$ composer require thecodingmachine/gotenberg-php-client
</pre> </pre>
@@ -474,7 +474,7 @@ use TheCodingMachine\Gotenberg\HTMLRequest;
$client = new Client(&#39;http://localhost:3000&#39;, new \Http\Adapter\Guzzle6\Client()); $client = new Client(&#39;http://localhost:3000&#39;, new \Http\Adapter\Guzzle6\Client());
$index = DocumentFactory::makeFromPath(&#39;index.html&#39;, &#39;index.html&#39;); $index = DocumentFactory::makeFromPath(&#39;index.html&#39;, &#39;index.html&#39;);
$request = new HTMLRequest($index); $request = new HTMLRequest($index);
$dest = &#34;result.pdf&#34;; $dest = &#39;result.pdf&#39;;
$client-&gt;store($request, $dest); $client-&gt;store($request, $dest);
</pre> </pre>
@@ -569,7 +569,7 @@ $footer = DocumentFactory::makeFromPath(&#39;footer.html&#39;, &#39;footer.html&
$request = new HTMLRequest($index); $request = new HTMLRequest($index);
$request-&gt;setHeader($header); $request-&gt;setHeader($header);
$request-&gt;setFooter($footer); $request-&gt;setFooter($footer);
$dest = &#34;result.pdf&#34;; $dest = &#39;result.pdf&#39;;
$client-&gt;store($request, $dest); $client-&gt;store($request, $dest);
</pre> </pre>
@@ -665,7 +665,7 @@ $assets = [
]; ];
$request = new HTMLRequest($index); $request = new HTMLRequest($index);
$request-&gt;setAssets($assets); $request-&gt;setAssets($assets);
$dest = &#34;result.pdf&#34;; $dest = &#39;result.pdf&#39;;
$client-&gt;store($request, $dest); $client-&gt;store($request, $dest);
</pre> </pre>
@@ -731,7 +731,7 @@ $request = new HTMLRequest($index);
$request-&gt;setPaperSize(Request::A4); $request-&gt;setPaperSize(Request::A4);
$request-&gt;setMargins(Request::NO_MARGINS); $request-&gt;setMargins(Request::NO_MARGINS);
$request-&gt;setLandscape(true); $request-&gt;setLandscape(true);
$dest = &#34;result.pdf&#34;; $dest = &#39;result.pdf&#39;;
$client-&gt;store($request, $dest); $client-&gt;store($request, $dest);
</pre> </pre>
@@ -787,7 +787,7 @@ $client = new Client(&#39;http://localhost:3000&#39;, new \Http\Adapter\Guzzle6\
$index = DocumentFactory::makeFromPath(&#39;index.html&#39;, &#39;index.html&#39;); $index = DocumentFactory::makeFromPath(&#39;index.html&#39;, &#39;index.html&#39;);
$request = new HTMLRequest($index); $request = new HTMLRequest($index);
$request-&gt;setWaitDelay(5.5); $request-&gt;setWaitDelay(5.5);
$dest = &#34;result.pdf&#34;; $dest = &#39;result.pdf&#39;;
$client-&gt;store($request, $dest); $client-&gt;store($request, $dest);
</pre> </pre>
@@ -846,7 +846,7 @@ $client = new Client(&#39;http://localhost:3000&#39;, new \Http\Adapter\Guzzle6\
$index = DocumentFactory::makeFromPath(&#39;index.html&#39;, &#39;index.html&#39;); $index = DocumentFactory::makeFromPath(&#39;index.html&#39;, &#39;index.html&#39;);
$request = new HTMLRequest($index); $request = new HTMLRequest($index);
$request-&gt;setGoogleChromeRpccBufferSize(1048576); $request-&gt;setGoogleChromeRpccBufferSize(1048576);
$dest = &#34;result.pdf&#34;; $dest = &#39;result.pdf&#39;;
$client-&gt;store($request, $dest); $client-&gt;store($request, $dest);
</pre> </pre>
@@ -912,7 +912,65 @@ use TheCodingMachine\Gotenberg\URLRequest;
$client = new Client(&#39;http://localhost:3000&#39;, new \Http\Adapter\Guzzle6\Client()); $client = new Client(&#39;http://localhost:3000&#39;, new \Http\Adapter\Guzzle6\Client());
$request = new URLRequest(&#39;https://google.com&#39;); $request = new URLRequest(&#39;https://google.com&#39;);
$request-&gt;setMargins(Request::NO_MARGINS); $request-&gt;setMargins(Request::NO_MARGINS);
$dest = &#34;result.pdf&#34;; $dest = &#39;result.pdf&#39;;
$client-&gt;store($request, $dest);
</pre>
<h2 class="Heading"><a class="Anchor" aria-hidden="true" id="url.custom_http_headers" href="#url.custom_http_headers">
<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>Custom HTTP headers</h2>
<p>You may send your own HTTP headers to the <code>remoteURL</code>.</p>
<p>For instance, by adding the HTTP header <code>Gotenberg-Remoteurl-Your-Header</code> to your request,
the API will send a request to the <code>remoteURL</code> with the HTTP header <code>Your-Header</code>.</p>
<blockquote>
<p><strong>Attention:</strong> the API uses a canonical format for the HTTP headers:
it transforms the first
letter and any letter following a hyphen to upper case;
the rest are converted to lowercase. For example, the
canonical key for <code>accept-encoding</code> is <code>Accept-Encoding</code>.</p>
</blockquote>
<h3 class="Heading"><a class="Anchor" aria-hidden="true" id="url.custom_http_headers.c_url" href="#url.custom_http_headers.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/url <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> --header <span class="s1">&#39;Gotenberg-Remoteurl-Your-Header: Foo&#39;</span> <span class="se">\
</span><span class="se"></span> --form <span class="nv">remoteURL</span><span class="o">=</span>https://google.com <span class="se">\
</span><span class="se"></span> -o result.pdf
</pre>
<h3 class="Heading"><a class="Anchor" aria-hidden="true" id="url.custom_http_headers.go" href="#url.custom_http_headers.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="o">:=</span> <span class="nx">gotenberg</span><span class="p">.</span><span class="nf">NewURLRequest</span><span class="p">(</span><span class="s">&#34;https://google.com&#34;</span><span class="p">)</span>
<span class="nx">req</span><span class="p">.</span><span class="nf">AddRemoteURLHTTPHeader</span><span class="p">(</span><span class="s">&#34;Your-Header&#34;</span><span class="p">,</span> <span class="s">&#34;Foo&#34;</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="url.custom_http_headers.php" href="#url.custom_http_headers.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\URLRequest;
$client = new Client(&#39;http://localhost:3000&#39;, new \Http\Adapter\Guzzle6\Client());
$request = new URLRequest(&#39;https://google.com&#39;);
$request-&gt;addRemoteURLHTTPHeader(&#39;Your-Header&#39;, &#39;Foo&#39;)
$dest = &#39;result.pdf&#39;;
$client-&gt;store($request, $dest); $client-&gt;store($request, $dest);
</pre> </pre>
@@ -989,7 +1047,7 @@ $markdowns = [
DocumentFactory::makeFromPath(&#39;file.md&#39;, &#39;file.md&#39;), DocumentFactory::makeFromPath(&#39;file.md&#39;, &#39;file.md&#39;),
]; ];
$request = new MarkdownRequest($index, $markdowns); $request = new MarkdownRequest($index, $markdowns);
$dest = &#34;result.pdf&#34;; $dest = &#39;result.pdf&#39;;
$client-&gt;store($request, $dest); $client-&gt;store($request, $dest);
</pre> </pre>
@@ -1070,7 +1128,7 @@ $files = [
DocumentFactory::makeFromPath(&#39;document2.docx&#39;, &#39;document2.docx&#39;), DocumentFactory::makeFromPath(&#39;document2.docx&#39;, &#39;document2.docx&#39;),
]; ];
$request = new OfficeRequest($files); $request = new OfficeRequest($files);
$dest = &#34;result.pdf&#34;; $dest = &#39;result.pdf&#39;;
$client-&gt;store($request, $dest); $client-&gt;store($request, $dest);
</pre> </pre>
@@ -1123,7 +1181,7 @@ $files = [
]; ];
$request = new OfficeRequest($files); $request = new OfficeRequest($files);
$request-&gt;setLandscape(true); $request-&gt;setLandscape(true);
$dest = &#34;result.pdf&#34;; $dest = &#39;result.pdf&#39;;
$client-&gt;store($request, $dest); $client-&gt;store($request, $dest);
</pre> </pre>
@@ -1188,7 +1246,7 @@ $files = [
DocumentFactory::makeFromPath(&#39;file2.pdf&#39;, &#39;file2.pdf&#39;), DocumentFactory::makeFromPath(&#39;file2.pdf&#39;, &#39;file2.pdf&#39;),
]; ];
$request = new MergeRequest($files); $request = new MergeRequest($files);
$dest = &#34;result.pdf&#34;; $dest = &#39;result.pdf&#39;;
$client-&gt;store($request, $dest); $client-&gt;store($request, $dest);
</pre> </pre>
@@ -1251,7 +1309,7 @@ $client = new Client(&#39;http://localhost:3000&#39;, new \Http\Adapter\Guzzle6\
$index = DocumentFactory::makeFromPath(&#39;index.html&#39;, &#39;index.html&#39;); $index = DocumentFactory::makeFromPath(&#39;index.html&#39;, &#39;index.html&#39;);
$request = new HTMLRequest($index); $request = new HTMLRequest($index);
$request-&gt;setWaitTimeout(2.5); $request-&gt;setWaitTimeout(2.5);
$dest = &#34;result.pdf&#34;; $dest = &#39;result.pdf&#39;;
$client-&gt;store($request, $dest); $client-&gt;store($request, $dest);
</pre> </pre>
@@ -1326,13 +1384,9 @@ $resp = $client-&gt;post($request);
<p>You may also define this value globally: see the <a href="#environment_variables.default_webhook_url_timeout">environment variables</a> section.</p> <p>You may also define this value globally: see the <a href="#environment_variables.default_webhook_url_timeout">environment variables</a> section.</p>
</blockquote> </blockquote>
<h3 class="Heading"><a class="Anchor" aria-hidden="true" id="webhook.timeout.examples" href="#webhook.timeout.examples"> <h3 class="Heading"><a class="Anchor" aria-hidden="true" id="webhook.timeout.c_url" href="#webhook.timeout.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>
</a>Examples</h3> </a>cURL</h3>
<h4 class="Heading"><a class="Anchor" aria-hidden="true" id="webhook.timeout.examples.c_url" href="#webhook.timeout.examples.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</h4>
<pre class="chroma">$ curl --request POST <span class="se">\ <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> --url http://localhost:3000/convert/html <span class="se">\
@@ -1342,9 +1396,9 @@ $resp = $client-&gt;post($request);
</span><span class="se"></span> --form <span class="nv">webhookURLTimeout</span><span class="o">=</span>2.5 </span><span class="se"></span> --form <span class="nv">webhookURLTimeout</span><span class="o">=</span>2.5
</pre> </pre>
<h4 class="Heading"><a class="Anchor" aria-hidden="true" id="webhook.timeout.examples.go" href="#webhook.timeout.examples.go"> <h3 class="Heading"><a class="Anchor" aria-hidden="true" id="webhook.timeout.go" href="#webhook.timeout.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> <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</h4> </a>Go</h3>
<pre class="chroma"><span class="kn">import</span> <span class="s">&#34;github.com/thecodingmachine/gotenberg-go-client/v6&#34;</span> <pre class="chroma"><span class="kn">import</span> <span class="s">&#34;github.com/thecodingmachine/gotenberg-go-client/v6&#34;</span>
@@ -1357,9 +1411,9 @@ $resp = $client-&gt;post($request);
<span class="p">}</span> <span class="p">}</span>
</pre> </pre>
<h4 class="Heading"><a class="Anchor" aria-hidden="true" id="webhook.timeout.examples.php" href="#webhook.timeout.examples.php"> <h3 class="Heading"><a class="Anchor" aria-hidden="true" id="webhook.timeout.php" href="#webhook.timeout.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> <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</h4> </a>PHP</h3>
<pre class="chroma">use TheCodingMachine\Gotenberg\Client; <pre class="chroma">use TheCodingMachine\Gotenberg\Client;
use TheCodingMachine\Gotenberg\DocumentFactory; use TheCodingMachine\Gotenberg\DocumentFactory;
@@ -1371,6 +1425,66 @@ $request = new HTMLRequest($index);
$request-&gt;setWebhookURL(&#39;http://myapp.com/webhook/&#39;); $request-&gt;setWebhookURL(&#39;http://myapp.com/webhook/&#39;);
$request-&gt;setWebhookURLTimeout(2.5); $request-&gt;setWebhookURLTimeout(2.5);
$resp = $client-&gt;post($request); $resp = $client-&gt;post($request);
</pre>
<h2 class="Heading"><a class="Anchor" aria-hidden="true" id="webhook.custom_http_headers" href="#webhook.custom_http_headers">
<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>Custom HTTP headers</h2>
<p>You may send your own HTTP headers to the <code>webhookURL</code>.</p>
<p>For instance, by adding the HTTP header <code>Gotenberg-Webhookurl-Your-Header</code> to your request,
the API will send a request to the <code>webhookURL</code> with the HTTP header <code>Your-Header</code>.</p>
<blockquote>
<p><strong>Attention:</strong> the API uses a canonical format for the HTTP headers:
it transforms the first
letter and any letter following a hyphen to upper case;
the rest are converted to lowercase. For example, the
canonical key for <code>accept-encoding</code> is <code>Accept-Encoding</code>.</p>
</blockquote>
<h3 class="Heading"><a class="Anchor" aria-hidden="true" id="webhook.custom_http_headers.c_url" href="#webhook.custom_http_headers.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> --header <span class="s1">&#39;Gotenberg-Webhookurl-Your-Header: Foo&#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">webhookURL</span><span class="o">=</span><span class="s1">&#39;http://myapp.com/webhook/&#39;</span>
</pre>
<h3 class="Heading"><a class="Anchor" aria-hidden="true" id="webhook.custom_http_headers.go" href="#webhook.custom_http_headers.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">WebhookURL</span><span class="p">(</span><span class="s">&#34;http://myapp.com/webhook/&#34;</span><span class="p">)</span>
<span class="nx">req</span><span class="p">.</span><span class="nf">AddWebhookURLHTTPHeader</span><span class="p">(</span><span class="s">&#34;Your-Header&#34;</span><span class="p">,</span> <span class="s">&#34;Foo&#34;</span><span class="p">)</span>
<span class="nx">resp</span><span class="p">,</span> <span class="nx">_</span> <span class="o">:=</span> <span class="nx">c</span><span class="p">.</span><span class="nf">Post</span><span class="p">(</span><span class="nx">req</span><span class="p">)</span>
<span class="p">}</span>
</pre>
<h3 class="Heading"><a class="Anchor" aria-hidden="true" id="webhook.custom_http_headers.php" href="#webhook.custom_http_headers.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;
$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;setWebhookURL(&#39;http://myapp.com/webhook/&#39;);
$request-&gt;addWebhookURLHTTPHeader(&#39;Your-Header&#39;, &#39;Foo&#39;);
$resp = $client-&gt;post($request);
</pre> </pre>
</div> </div>

View File

@@ -138,6 +138,7 @@ func urlHandler(c echo.Context) error {
if err != nil { if err != nil {
return err return err
} }
opts.CustomHTTPHeaders = resource.RemoteURLCustomHTTPHeaders(r)
if !r.HasArg(resource.RemoteURLArgKey) { if !r.HasArg(resource.RemoteURLArgKey) {
return xerror.Invalid( return xerror.Invalid(
op, op,
@@ -322,20 +323,50 @@ func convertAsync(ctx context.Context, p printer.Printer, filename, fpath string
defer f.Close() // nolint: errcheck defer f.Close() // nolint: errcheck
logger.DebugfOp( logger.DebugfOp(
op, op,
"sending result file '%s' to '%s'", "preparing to send result file '%s' to '%s'...",
filename, filename,
webhookURL, webhookURL,
) )
httpClient := &http.Client{ httpClient := &http.Client{
Timeout: xtime.Duration(webhookURLTimeout), Timeout: xtime.Duration(webhookURLTimeout),
} }
resp, err := httpClient.Post(webhookURL, "application/pdf", f) /* #nosec */ req, err := http.NewRequest(http.MethodPost, webhookURL, f)
if err != nil {
xerr := xerror.New(op, err)
logger.ErrorOp(xerror.Op(xerr), xerr)
return
}
req.Header.Set(echo.HeaderContentType, "application/pdf")
// set custom headers (if any).
customHTTPHeaders := resource.WebhookURLCustomHTTPHeaders(r)
if len(customHTTPHeaders) > 0 {
for key, value := range customHTTPHeaders {
req.Header.Set(key, value)
logger.DebugfOp(op, "set '%s' to custom HTTP header '%s'", value, key)
}
} else {
logger.DebugOp(op, "skipping custom HTTP headers as none have been provided...")
}
// send the result file.
logger.DebugfOp(
op,
"sending result file '%s' to '%s'...",
filename,
webhookURL,
)
resp, err := httpClient.Do(req) /* #nosec */
if err != nil { if err != nil {
xerr := xerror.New(op, err) xerr := xerror.New(op, err)
logger.ErrorOp(xerror.Op(xerr), xerr) logger.ErrorOp(xerror.Op(xerr), xerr)
return return
} }
defer resp.Body.Close() // nolint: errcheck defer resp.Body.Close() // nolint: errcheck
logger.DebugfOp(
op,
"result file '%s' sent to '%s'",
filename,
webhookURL,
)
}() }()
return nil return nil
} }

View File

@@ -581,11 +581,18 @@ func TestOfficeHandler(t *testing.T) {
} }
func TestWebhook(t *testing.T) { func TestWebhook(t *testing.T) {
customHeaderRealKey := http.CanonicalHeaderKey("MyCustomHeader")
customHeaderKey := fmt.Sprintf("%s%s", resource.WebhookURLCustomHTTPHeaderCanonicalBaseKey, customHeaderRealKey)
customHeaderValue := "foo"
status := make(chan error, 2) status := make(chan error, 2)
rcv := echo.New() rcv := echo.New()
rcv.POST("/foo", func(c echo.Context) error { rcv.POST("/foo", func(c echo.Context) error {
if c.Request().Header.Get("Content-type") != "application/pdf" { if c.Request().Header.Get(echo.HeaderContentType) != "application/pdf" {
status <- fmt.Errorf("wrong Content-type: got %s want %s", c.Request().Header.Get("Content-type"), "application/pdf") status <- fmt.Errorf("wrong Content-type: got '%s' want '%s'", c.Request().Header.Get(echo.HeaderContentType), "application/pdf")
return nil
}
if c.Request().Header.Get(customHeaderRealKey) != customHeaderValue {
status <- fmt.Errorf("wrong '%s': got '%s' want '%s'", customHeaderRealKey, c.Request().Header.Get(customHeaderRealKey), customHeaderValue)
return nil return nil
} }
body, err := ioutil.ReadAll(c.Request().Body) body, err := ioutil.ReadAll(c.Request().Body)
@@ -609,6 +616,7 @@ func TestWebhook(t *testing.T) {
body, contentType := test.MergeMultipartForm(t, map[string]string{string(resource.WebhookURLArgKey): "http://localhost:3001/foo"}) body, contentType := test.MergeMultipartForm(t, map[string]string{string(resource.WebhookURLArgKey): "http://localhost:3001/foo"})
req := httptest.NewRequest(http.MethodPost, mergeEndpoint(config), body) req := httptest.NewRequest(http.MethodPost, mergeEndpoint(config), body)
req.Header.Set(echo.HeaderContentType, contentType) req.Header.Set(echo.HeaderContentType, contentType)
req.Header.Set(customHeaderKey, customHeaderValue)
test.AssertStatusCode(t, http.StatusOK, srv, req) test.AssertStatusCode(t, http.StatusOK, srv, req)
err := <-status err := <-status
assert.NoError(t, err) assert.NoError(t, err)
@@ -622,5 +630,5 @@ func TestResultFilename(t *testing.T) {
req.Header.Set(echo.HeaderContentType, contentType) req.Header.Set(echo.HeaderContentType, contentType)
rec := httptest.NewRecorder() rec := httptest.NewRecorder()
srv.ServeHTTP(rec, req) srv.ServeHTTP(rec, req)
assert.Equal(t, "attachment; filename=\"foo.pdf\"", rec.Header().Get("Content-Disposition")) assert.Equal(t, "attachment; filename=\"foo.pdf\"", rec.Header().Get(echo.HeaderContentDisposition))
} }

View File

@@ -12,7 +12,6 @@ import (
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"github.com/thecodingmachine/gotenberg/internal/app/xhttp/pkg/resource" "github.com/thecodingmachine/gotenberg/internal/app/xhttp/pkg/resource"
"github.com/thecodingmachine/gotenberg/internal/pkg/conf" "github.com/thecodingmachine/gotenberg/internal/pkg/conf"
"github.com/thecodingmachine/gotenberg/internal/pkg/normalize"
"github.com/thecodingmachine/gotenberg/internal/pkg/xerror" "github.com/thecodingmachine/gotenberg/internal/pkg/xerror"
"github.com/thecodingmachine/gotenberg/internal/pkg/xlog" "github.com/thecodingmachine/gotenberg/internal/pkg/xlog"
) )
@@ -79,6 +78,10 @@ func (ctx *Context) WithResource(directoryName string) error {
if err != nil { if err != nil {
return r, err return r, err
} }
// retrieve custom headers from request.
for key, value := range ctx.Request().Header {
r.WithCustomHTTPHeader(key, value[0])
}
// retrieve form values from request. // retrieve form values from request.
for _, key := range resource.ArgKeys() { for _, key := range resource.ArgKeys() {
r.WithArg(key, ctx.FormValue(string(key))) r.WithArg(key, ctx.FormValue(string(key)))
@@ -103,11 +106,7 @@ func (ctx *Context) WithResource(directoryName string) error {
return r, err return r, err
} }
defer in.Close() // nolint: errcheck defer in.Close() // nolint: errcheck
filename, err := normalize.String(fh.Filename) if err := r.WithFile(fh.Filename, in); err != nil {
if err != nil {
return r, err
}
if err := r.WithFile(filename, in); err != nil {
return r, err return r, err
} }
} }

View File

@@ -0,0 +1,37 @@
package resource
import (
"strings"
)
const (
// RemoteURLCustomHTTPHeaderCanonicalBaseKey is the base key
// of custom headers send to the remote URL.
RemoteURLCustomHTTPHeaderCanonicalBaseKey string = "Gotenberg-Remoteurl-"
// WebhookURLCustomHTTPHeaderCanonicalBaseKey is the base key
// of custom headers send to the webhook URL.
WebhookURLCustomHTTPHeaderCanonicalBaseKey string = "Gotenberg-Webhookurl-"
)
func fetchCustomHTTPHeaders(r Resource, baseKey string) map[string]string {
customHeaders := make(map[string]string)
for key, value := range r.customHeaders {
if strings.Contains(key, baseKey) {
realKey := strings.Replace(key, baseKey, "", 1)
customHeaders[realKey] = value
}
}
return customHeaders
}
// RemoteURLCustomHTTPHeaders is a helper for retrieving
// the custom headers for the URL conversion.
func RemoteURLCustomHTTPHeaders(r Resource) map[string]string {
return fetchCustomHTTPHeaders(r, RemoteURLCustomHTTPHeaderCanonicalBaseKey)
}
// WebhookURLCustomHTTPHeaders is a helper for retrieving
// the custom headers for the webhook URL.
func WebhookURLCustomHTTPHeaders(r Resource) map[string]string {
return fetchCustomHTTPHeaders(r, WebhookURLCustomHTTPHeaderCanonicalBaseKey)
}

View File

@@ -0,0 +1,54 @@
package resource
import (
"fmt"
"net/http"
"testing"
"github.com/stretchr/testify/assert"
"github.com/thecodingmachine/gotenberg/test"
)
func TestRemoteURLCustomHeaders(t *testing.T) {
const resourceDirectoryName string = "foo"
logger := test.DebugLogger()
r, err := New(logger, resourceDirectoryName)
assert.Nil(t, err)
// should find the custom header.
customHeaderValue := "bar"
customHeaderCanonicalRealKey := "Foo"
customHeaderCanonicalKey := http.CanonicalHeaderKey(fmt.Sprintf("%s%s", RemoteURLCustomHTTPHeaderCanonicalBaseKey, customHeaderCanonicalRealKey))
r.WithCustomHTTPHeader(customHeaderCanonicalKey, customHeaderValue)
r.WithCustomHTTPHeader("Bar", "Bar")
expected := map[string]string{
customHeaderCanonicalRealKey: customHeaderValue,
}
notExpected := map[string]string{
customHeaderCanonicalKey: customHeaderValue,
}
v := RemoteURLCustomHTTPHeaders(r)
assert.Equal(t, expected, v)
assert.NotEqual(t, notExpected, v)
}
func TestWebhookURLCustomHeaders(t *testing.T) {
const resourceDirectoryName string = "foo"
logger := test.DebugLogger()
r, err := New(logger, resourceDirectoryName)
assert.Nil(t, err)
// should find the custom header.
customHeaderValue := "bar"
customHeaderCanonicalRealKey := "Foo"
customHeaderCanonicalKey := http.CanonicalHeaderKey(fmt.Sprintf("%s%s", WebhookURLCustomHTTPHeaderCanonicalBaseKey, customHeaderCanonicalRealKey))
r.WithCustomHTTPHeader(customHeaderCanonicalKey, customHeaderValue)
r.WithCustomHTTPHeader("Bar", "Bar")
expected := map[string]string{
customHeaderCanonicalRealKey: customHeaderValue,
}
notExpected := map[string]string{
customHeaderCanonicalKey: customHeaderValue,
}
v := WebhookURLCustomHTTPHeaders(r)
assert.Equal(t, expected, v)
assert.NotEqual(t, notExpected, v)
}

View File

@@ -3,9 +3,12 @@ package resource
import ( import (
"fmt" "fmt"
"io" "io"
"net/http"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"github.com/thecodingmachine/gotenberg/internal/pkg/normalize"
"github.com/thecodingmachine/gotenberg/internal/pkg/xassert" "github.com/thecodingmachine/gotenberg/internal/pkg/xassert"
"github.com/thecodingmachine/gotenberg/internal/pkg/xerror" "github.com/thecodingmachine/gotenberg/internal/pkg/xerror"
"github.com/thecodingmachine/gotenberg/internal/pkg/xlog" "github.com/thecodingmachine/gotenberg/internal/pkg/xlog"
@@ -21,10 +24,11 @@ const TemporaryDirectory string = "tmp"
// Resource helps managing // Resource helps managing
// arguments and files for a conversion. // arguments and files for a conversion.
type Resource struct { type Resource struct {
logger xlog.Logger logger xlog.Logger
dirPath string dirPath string
args map[ArgKey]string customHeaders map[string]string
files map[string]file args map[ArgKey]string
files map[string]file
} }
// New creates a Resource where its files will // New creates a Resource where its files will
@@ -48,10 +52,11 @@ func New(logger xlog.Logger, directoryName string) (Resource, error) {
} }
logger.DebugfOp(op, "resource directory '%s' created", directoryName) logger.DebugfOp(op, "resource directory '%s' created", directoryName)
return Resource{ return Resource{
logger: logger, logger: logger,
dirPath: dirPath, dirPath: dirPath,
args: make(map[ArgKey]string), customHeaders: make(map[string]string),
files: make(map[string]file), args: make(map[ArgKey]string),
files: make(map[string]file),
}, nil }, nil
} }
@@ -70,6 +75,21 @@ func (r Resource) Close() error {
return nil return nil
} }
// WithCustomHTTPHeader add a new custom header to the Resource.
// Given key should be in canonical format.
func (r *Resource) WithCustomHTTPHeader(key string, value string) {
const op string = "resource.Resource.WithCustomHTTPHeader"
// should already be in canonical format.
canonicalKey := http.CanonicalHeaderKey(key)
if strings.Contains(canonicalKey, RemoteURLCustomHTTPHeaderCanonicalBaseKey) ||
strings.Contains(canonicalKey, WebhookURLCustomHTTPHeaderCanonicalBaseKey) {
r.customHeaders[canonicalKey] = value
r.logger.DebugfOp(op, "added '%s' with value '%s' to resource custom HTTP headers", canonicalKey, value)
return
}
r.logger.DebugfOp(op, "skipping '%s' as it is not a custom HTTP header...", canonicalKey)
}
// WithArg add a new argument to the Resource. // WithArg add a new argument to the Resource.
func (r *Resource) WithArg(key ArgKey, value string) { func (r *Resource) WithArg(key ArgKey, value string) {
const op string = "resource.Resource.WithArg" const op string = "resource.Resource.WithArg"
@@ -80,13 +100,24 @@ func (r *Resource) WithArg(key ArgKey, value string) {
// WithFile add a new file to the Resource. // WithFile add a new file to the Resource.
func (r *Resource) WithFile(filename string, in io.Reader) error { func (r *Resource) WithFile(filename string, in io.Reader) error {
const op string = "resource.Resource.WithFile" const op string = "resource.Resource.WithFile"
fpath := fmt.Sprintf("%s/%s", r.dirPath, filename) resolver := func() error {
file := file{fpath: fpath} // see https://github.com/thecodingmachine/gotenberg/issues/104.
if err := file.write(in); err != nil { normalized, err := normalize.String(filename)
if err != nil {
return err
}
fpath := fmt.Sprintf("%s/%s", r.dirPath, normalized)
file := file{fpath: fpath}
if err := file.write(in); err != nil {
return err
}
r.files[filename] = file
r.logger.DebugfOp(op, "resource file '%s' created", filename)
return nil
}
if err := resolver(); err != nil {
return xerror.New(op, err) return xerror.New(op, err)
} }
r.files[filename] = file
r.logger.DebugfOp(op, "resource file '%s' created", filename)
return nil return nil
} }

View File

@@ -2,6 +2,7 @@ package printer
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"strings" "strings"
@@ -30,18 +31,19 @@ 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 RpccBufferSize int64
CustomHTTPHeaders map[string]string
} }
// DefaultChromePrinterOptions returns the default // DefaultChromePrinterOptions returns the default
@@ -49,18 +51,19 @@ 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(), RpccBufferSize: config.DefaultGoogleChromeRpccBufferSize(),
CustomHTTPHeaders: make(map[string]string),
} }
} }
@@ -144,6 +147,10 @@ func (p chromePrinter) Print(destination string) error {
if err := p.enableEvents(ctx, targetClient); err != nil { if err := p.enableEvents(ctx, targetClient); err != nil {
return err return err
} }
// add custom headers (if any).
if err := p.setCustomHTTPHeaders(ctx, targetClient); err != nil {
return err
}
// listen for all events. // listen for all events.
if err := p.listenEvents(ctx, targetClient); err != nil { if err := p.listenEvents(ctx, targetClient); err != nil {
return err return err
@@ -247,6 +254,32 @@ func (p chromePrinter) enableEvents(ctx context.Context, client *cdp.Client) err
return nil return nil
} }
func (p chromePrinter) setCustomHTTPHeaders(ctx context.Context, client *cdp.Client) error {
const op string = "printer.chromePrinter.setCustomHTTPHeaders"
resolver := func() error {
if len(p.opts.CustomHTTPHeaders) == 0 {
p.logger.DebugOp(op, "skipping custom HTTP headers as none have been provided...")
return nil
}
customHTTPHeaders := make(map[string]string)
// useless but for the logs.
for key, value := range p.opts.CustomHTTPHeaders {
customHTTPHeaders[key] = value
p.logger.DebugfOp(op, "set '%s' to custom HTTP header '%s'", value, key)
}
b, err := json.Marshal(customHTTPHeaders)
if err != nil {
return err
}
// should always be called after client.Network.Enable.
return client.Network.SetExtraHTTPHeaders(ctx, network.NewSetExtraHTTPHeadersArgs(b))
}
if err := resolver(); err != nil {
return xerror.New(op, err)
}
return nil
}
func (p chromePrinter) listenEvents(ctx context.Context, client *cdp.Client) error { func (p chromePrinter) listenEvents(ctx context.Context, client *cdp.Client) error {
const op string = "printer.chromePrinter.listenEvents" const op string = "printer.chromePrinter.listenEvents"
resolver := func() error { resolver := func() error {

View File

@@ -79,7 +79,7 @@ func multipartForm(
require.Nil(t, err) require.Nil(t, err)
} }
if kind == "url" { if kind == "url" {
err := writer.WriteField("remoteURL", "http://google.com") err := writer.WriteField("remoteURL", "https://google.com")
require.Nil(t, err) require.Nil(t, err)
} }
for k, v := range formValues { for k, v := range formValues {

View File

@@ -75,6 +75,7 @@ func OfficeFpaths(t *testing.T) []string {
fpath(t, "office", "document.docx"), fpath(t, "office", "document.docx"),
fpath(t, "office", "document.rtf"), fpath(t, "office", "document.rtf"),
fpath(t, "office", "document.txt"), fpath(t, "office", "document.txt"),
fpath(t, "office", "document_with_special_éà.txt"),
} }
} }

View File

@@ -0,0 +1,3 @@
Gutenberg
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.