mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-18 05:02:15 +01:00
updating with base branch
This commit is contained in:
@@ -34,35 +34,47 @@ const (
|
||||
// LogLevelEnvVar contains the name
|
||||
// of the environment variable "LOG_LEVEL".
|
||||
LogLevelEnvVar string = "LOG_LEVEL"
|
||||
// RootPathEnvVar contains the name
|
||||
// of the environment variable "ROOT_PATH".
|
||||
RootPathEnvVar string = "ROOT_PATH"
|
||||
// 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
|
||||
// configuration.
|
||||
type Config struct {
|
||||
maximumWaitTimeout float64
|
||||
maximumWaitDelay float64
|
||||
maximumWebhookURLTimeout float64
|
||||
defaultWaitTimeout float64
|
||||
defaultWebhookURLTimeout float64
|
||||
defaultListenPort int64
|
||||
disableGoogleChrome bool
|
||||
disableUnoconv bool
|
||||
logLevel xlog.Level
|
||||
maximumWaitTimeout float64
|
||||
maximumWaitDelay float64
|
||||
maximumWebhookURLTimeout float64
|
||||
defaultWaitTimeout float64
|
||||
defaultWebhookURLTimeout float64
|
||||
defaultListenPort int64
|
||||
disableGoogleChrome bool
|
||||
disableUnoconv bool
|
||||
logLevel xlog.Level
|
||||
rootPath string
|
||||
maximumGoogleChromeRpccBufferSize int64
|
||||
defaultGoogleChromeRpccBufferSize int64
|
||||
}
|
||||
|
||||
// DefaultConfig returns the default
|
||||
// configuration.
|
||||
func DefaultConfig() Config {
|
||||
return Config{
|
||||
maximumWaitTimeout: 30.0,
|
||||
maximumWaitDelay: 10.0,
|
||||
maximumWebhookURLTimeout: 30.0,
|
||||
defaultWaitTimeout: 10.0,
|
||||
defaultWebhookURLTimeout: 10.0,
|
||||
defaultListenPort: 3000,
|
||||
disableGoogleChrome: false,
|
||||
disableUnoconv: false,
|
||||
logLevel: xlog.InfoLevel,
|
||||
maximumWaitTimeout: 30.0,
|
||||
maximumWaitDelay: 10.0,
|
||||
maximumWebhookURLTimeout: 30.0,
|
||||
defaultWaitTimeout: 10.0,
|
||||
defaultWebhookURLTimeout: 10.0,
|
||||
defaultListenPort: 3000,
|
||||
disableGoogleChrome: false,
|
||||
disableUnoconv: false,
|
||||
logLevel: xlog.InfoLevel,
|
||||
rootPath: "/",
|
||||
maximumGoogleChromeRpccBufferSize: 104857600, // ~100 MB
|
||||
defaultGoogleChromeRpccBufferSize: 1048576, // 1 MB
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,6 +168,26 @@ func FromEnv() (Config, error) {
|
||||
if err != nil {
|
||||
return c, err
|
||||
}
|
||||
rootPath, err := xassert.StringFromEnv(
|
||||
RootPathEnvVar,
|
||||
c.rootPath,
|
||||
xassert.StringStartWith("/"),
|
||||
xassert.StringEndWith("/"),
|
||||
)
|
||||
c.rootPath = rootPath
|
||||
if err != nil {
|
||||
return c, err
|
||||
}
|
||||
defaultGoogleChromeRpccBufferSize, err := xassert.Int64FromEnv(
|
||||
DefaultGoogleChromeRpccBufferSizeEnvVar,
|
||||
c.defaultGoogleChromeRpccBufferSize,
|
||||
xassert.Int64NotInferiorTo(0),
|
||||
xassert.Int64NotSuperiorTo(c.MaximumGoogleChromeRpccBufferSize()),
|
||||
)
|
||||
c.defaultGoogleChromeRpccBufferSize = defaultGoogleChromeRpccBufferSize
|
||||
if err != nil {
|
||||
return c, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
result, err := resolver()
|
||||
@@ -224,3 +256,21 @@ func (c Config) DisableUnoconv() bool {
|
||||
func (c Config) LogLevel() xlog.Level {
|
||||
return c.logLevel
|
||||
}
|
||||
|
||||
// RootPath returns the rooth path from
|
||||
// the configuration.
|
||||
func (c Config) RootPath() string {
|
||||
return c.rootPath
|
||||
}
|
||||
|
||||
// MaximumGoogleChromeRpccBufferSize returns the maximum
|
||||
// Google Chrome rpcc buffer size from the configuration.
|
||||
func (c Config) MaximumGoogleChromeRpccBufferSize() int64 {
|
||||
return c.maximumGoogleChromeRpccBufferSize
|
||||
}
|
||||
|
||||
// DefaultGoogleChromeRpccBufferSize returns the default
|
||||
// Google Chrome rpcc buffer size from the configuration.
|
||||
func (c Config) DefaultGoogleChromeRpccBufferSize() int64 {
|
||||
return c.defaultGoogleChromeRpccBufferSize
|
||||
}
|
||||
|
||||
@@ -320,6 +320,66 @@ func TestLogLevelFromEnv(t *testing.T) {
|
||||
os.Unsetenv(LogLevelEnvVar)
|
||||
}
|
||||
|
||||
func TestRootPathFromEnv(t *testing.T) {
|
||||
var (
|
||||
expected Config
|
||||
result Config
|
||||
err error
|
||||
)
|
||||
// ROOT_PATH correctly set.
|
||||
os.Setenv(RootPathEnvVar, "/foo/")
|
||||
expected = DefaultConfig()
|
||||
expected.rootPath = "/foo/"
|
||||
result, err = FromEnv()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, expected, result)
|
||||
os.Unsetenv(RootPathEnvVar)
|
||||
// ROOT_PATH wrongly set.
|
||||
os.Setenv(RootPathEnvVar, "foo")
|
||||
expected = DefaultConfig()
|
||||
result, err = FromEnv()
|
||||
test.AssertError(t, err)
|
||||
assert.Equal(t, expected, result)
|
||||
os.Unsetenv(RootPathEnvVar)
|
||||
}
|
||||
|
||||
func TestDefaultGoogleChromeRpccBufferSizeFromEnv(t *testing.T) {
|
||||
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) {
|
||||
result := DefaultConfig()
|
||||
assert.Equal(t, result.maximumWaitTimeout, result.MaximumWaitTimeout())
|
||||
@@ -331,4 +391,7 @@ func TestGetters(t *testing.T) {
|
||||
assert.Equal(t, result.disableGoogleChrome, result.DisableGoogleChrome())
|
||||
assert.Equal(t, result.disableUnoconv, result.DisableUnoconv())
|
||||
assert.Equal(t, result.logLevel, result.LogLevel())
|
||||
assert.Equal(t, result.rootPath, result.RootPath())
|
||||
assert.Equal(t, result.maximumGoogleChromeRpccBufferSize, result.MaximumGoogleChromeRpccBufferSize())
|
||||
assert.Equal(t, result.defaultGoogleChromeRpccBufferSize, result.DefaultGoogleChromeRpccBufferSize())
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package printer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
@@ -30,18 +31,20 @@ type chromePrinter struct {
|
||||
// ChromePrinterOptions helps customizing the
|
||||
// Google Chrome Printer behaviour.
|
||||
type ChromePrinterOptions struct {
|
||||
WaitTimeout float64
|
||||
WaitDelay float64
|
||||
HeaderHTML string
|
||||
FooterHTML string
|
||||
PaperWidth float64
|
||||
PaperHeight float64
|
||||
MarginTop float64
|
||||
MarginBottom float64
|
||||
MarginLeft float64
|
||||
MarginRight float64
|
||||
Landscape bool
|
||||
PageRanges string
|
||||
WaitTimeout float64
|
||||
WaitDelay float64
|
||||
HeaderHTML string
|
||||
FooterHTML string
|
||||
PaperWidth float64
|
||||
PaperHeight float64
|
||||
MarginTop float64
|
||||
MarginBottom float64
|
||||
MarginLeft float64
|
||||
MarginRight float64
|
||||
Landscape bool
|
||||
PageRanges string
|
||||
RpccBufferSize int64
|
||||
CustomHTTPHeaders map[string]string
|
||||
}
|
||||
|
||||
// DefaultChromePrinterOptions returns the default
|
||||
@@ -49,18 +52,20 @@ type ChromePrinterOptions struct {
|
||||
func DefaultChromePrinterOptions(config conf.Config) ChromePrinterOptions {
|
||||
const defaultHeaderFooterHTML string = "<html><head></head><body></body></html>"
|
||||
return ChromePrinterOptions{
|
||||
WaitTimeout: config.DefaultWaitTimeout(),
|
||||
WaitDelay: 0.0,
|
||||
HeaderHTML: defaultHeaderFooterHTML,
|
||||
FooterHTML: defaultHeaderFooterHTML,
|
||||
PaperWidth: 8.27,
|
||||
PaperHeight: 11.7,
|
||||
MarginTop: 1.0,
|
||||
MarginBottom: 1.0,
|
||||
MarginLeft: 1.0,
|
||||
MarginRight: 1.0,
|
||||
Landscape: false,
|
||||
PageRanges: "",
|
||||
WaitTimeout: config.DefaultWaitTimeout(),
|
||||
WaitDelay: 0.0,
|
||||
HeaderHTML: defaultHeaderFooterHTML,
|
||||
FooterHTML: defaultHeaderFooterHTML,
|
||||
PaperWidth: 8.27,
|
||||
PaperHeight: 11.7,
|
||||
MarginTop: 1.0,
|
||||
MarginBottom: 1.0,
|
||||
MarginLeft: 1.0,
|
||||
MarginRight: 1.0,
|
||||
Landscape: false,
|
||||
PageRanges: "",
|
||||
RpccBufferSize: config.DefaultGoogleChromeRpccBufferSize(),
|
||||
CustomHTTPHeaders: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +118,18 @@ func (p chromePrinter) Print(destination string) error {
|
||||
}
|
||||
// connect the client to the new target.
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
@@ -133,6 +149,10 @@ func (p chromePrinter) Print(destination string) error {
|
||||
if err := p.enableEvents(ctx, targetClient); err != nil {
|
||||
return err
|
||||
}
|
||||
// add custom headers (if any).
|
||||
if err := p.setCustomHTTPHeaders(ctx, targetClient); err != nil {
|
||||
return err
|
||||
}
|
||||
// listen for all events.
|
||||
if err := p.listenEvents(ctx, targetClient); err != nil {
|
||||
return err
|
||||
@@ -166,7 +186,7 @@ func (p chromePrinter) Print(destination string) error {
|
||||
printToPdfArgs,
|
||||
)
|
||||
if err != nil {
|
||||
// TODO: find a way to check it in the handlers.
|
||||
// find a way to check it in the handlers?
|
||||
if strings.Contains(err.Error(), "Page range syntax error") {
|
||||
return xerror.Invalid(
|
||||
op,
|
||||
@@ -174,6 +194,16 @@ func (p chromePrinter) Print(destination string) error {
|
||||
err,
|
||||
)
|
||||
}
|
||||
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
|
||||
}
|
||||
if err := ioutil.WriteFile(destination, print.Data, 0644); err != nil {
|
||||
@@ -238,6 +268,32 @@ func (p chromePrinter) enableEvents(ctx context.Context, client *cdp.Client) err
|
||||
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 {
|
||||
const op string = "printer.chromePrinter.listenEvents"
|
||||
resolver := func() error {
|
||||
|
||||
@@ -2,6 +2,7 @@ package printer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/conf"
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/xcontext"
|
||||
@@ -54,6 +55,8 @@ func (p mergePrinter) Print(destination string) error {
|
||||
defer cancel()
|
||||
p.ctx = ctx
|
||||
}
|
||||
// see https://github.com/thecodingmachine/gotenberg/issues/139.
|
||||
sort.Strings(p.fpaths)
|
||||
p.logger.DebugfOp(op, "merging '%v'...", p.fpaths)
|
||||
resolver := func() error {
|
||||
var args []string
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/phayes/freeport"
|
||||
@@ -56,6 +57,8 @@ func (p officePrinter) Print(destination string) error {
|
||||
ctx, cancel := xcontext.WithTimeout(p.logger, p.opts.WaitTimeout)
|
||||
defer cancel()
|
||||
resolver := func() error {
|
||||
// see https://github.com/thecodingmachine/gotenberg/issues/139.
|
||||
sort.Strings(p.fpaths)
|
||||
fpaths := make([]string, len(p.fpaths))
|
||||
dirPath := filepath.Dir(destination)
|
||||
for i, fpath := range p.fpaths {
|
||||
@@ -111,7 +114,7 @@ func (p officePrinter) unoconv(ctx context.Context, fpath, destination string) e
|
||||
}
|
||||
args = append(args, "--output", destination, fpath)
|
||||
if err := xexec.Run(ctx, p.logger, "unoconv", args...); err != nil {
|
||||
// TODO: find a way to check it in the handlers.
|
||||
// find a way to check it in the handlers?
|
||||
if p.opts.PageRanges != "" && strings.Contains(err.Error(), "exit status 5") {
|
||||
return xerror.Invalid(
|
||||
op,
|
||||
|
||||
@@ -2,6 +2,7 @@ package xassert
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/xerror"
|
||||
)
|
||||
@@ -54,7 +55,67 @@ func StringOneOf(values []string) RuleString {
|
||||
}
|
||||
}
|
||||
|
||||
type ruleStringStartWith struct {
|
||||
*baseRuleString
|
||||
startWith string
|
||||
}
|
||||
|
||||
func (r ruleStringStartWith) validate() error {
|
||||
const op string = "xassert.ruleStringStartWith.validate"
|
||||
if strings.HasPrefix(r.value, r.startWith) {
|
||||
return nil
|
||||
}
|
||||
return xerror.Invalid(
|
||||
op,
|
||||
fmt.Sprintf("'%s' should start with '%s', got '%s'", r.key, r.startWith, r.value),
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
||||
/*
|
||||
StringStartWith returns a RuleString for
|
||||
validating that a string starts with
|
||||
given string.
|
||||
*/
|
||||
func StringStartWith(startWith string) RuleString {
|
||||
return ruleStringStartWith{
|
||||
&baseRuleString{},
|
||||
startWith,
|
||||
}
|
||||
}
|
||||
|
||||
type ruleStringEndWith struct {
|
||||
*baseRuleString
|
||||
endWith string
|
||||
}
|
||||
|
||||
func (r ruleStringEndWith) validate() error {
|
||||
const op string = "xassert.ruleStringEndWith.validate"
|
||||
if strings.HasSuffix(r.value, r.endWith) {
|
||||
return nil
|
||||
}
|
||||
return xerror.Invalid(
|
||||
op,
|
||||
fmt.Sprintf("'%s' should end with '%s', got '%s'", r.key, r.endWith, r.value),
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
||||
/*
|
||||
StringEndWith returns a RuleString for
|
||||
validating that a string ends with
|
||||
given string.
|
||||
*/
|
||||
func StringEndWith(endWith string) RuleString {
|
||||
return ruleStringEndWith{
|
||||
&baseRuleString{},
|
||||
endWith,
|
||||
}
|
||||
}
|
||||
|
||||
// Compile-time checks to ensure type implements desired interfaces.
|
||||
var (
|
||||
_ = RuleString(new(ruleStringOneOf))
|
||||
_ = RuleString(new(ruleStringStartWith))
|
||||
_ = RuleString(new(ruleStringEndWith))
|
||||
)
|
||||
|
||||
@@ -18,3 +18,27 @@ func TestStringOfOne(t *testing.T) {
|
||||
err = rule.validate()
|
||||
test.AssertError(t, err)
|
||||
}
|
||||
|
||||
func TestStringStartWith(t *testing.T) {
|
||||
rule := StringStartWith("foo")
|
||||
// should be OK.
|
||||
rule.with("FOO", "foobarfoo")
|
||||
err := rule.validate()
|
||||
assert.Nil(t, err)
|
||||
// should not be OK.
|
||||
rule.with("FOO", "qux")
|
||||
err = rule.validate()
|
||||
test.AssertError(t, err)
|
||||
}
|
||||
|
||||
func TestStringEndWith(t *testing.T) {
|
||||
rule := StringEndWith("foo")
|
||||
// should be OK.
|
||||
rule.with("FOO", "foobarfoo")
|
||||
err := rule.validate()
|
||||
assert.Nil(t, err)
|
||||
// should not be OK.
|
||||
rule.with("FOO", "qux")
|
||||
err = rule.validate()
|
||||
test.AssertError(t, err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user