updating with base branch

This commit is contained in:
Julien Neuhart
2019-12-10 15:17:25 +01:00
47 changed files with 1728 additions and 594 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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