updating with current version of 6.1.0 branch

This commit is contained in:
Julien Neuhart
2019-12-09 14:38:31 +01:00
13 changed files with 272 additions and 62 deletions

View File

@@ -5,7 +5,7 @@ DOCKER_PASSWORD=
DOCKER_REGISTRY=thecodingmachine
GOTENBERG_USER_GID=1001
GOTENBERG_USER_UID=1001
GOLANGCI_LINT_VERSION=1.19.1
GOLANGCI_LINT_VERSION=1.20.1
CODE_COVERAGE=0
TINI_VERSION=0.18.0
MAXIMUM_WAIT_TIMEOUT=30.0
@@ -17,6 +17,7 @@ DEFAULT_LISTEN_PORT=3000
DISABLE_GOOGLE_CHROME=0
DISABLE_UNOCONV=0
LOG_LEVEL=INFO
ROOT_PATH=/
DEFAULT_GOOGLE_CHROME_RPCC_BUFFER_SIZE=1048576
# build the base Docker image.
@@ -57,7 +58,7 @@ image:
# start the API using previously built Docker image.
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) -e DEFAULT_GOOGLE_CHROME_RPCC_BUFFER_SIZE=$(DEFAULT_GOOGLE_CHROME_RPCC_BUFFER_SIZE) -p "$(DEFAULT_LISTEN_PORT):$(DEFAULT_LISTEN_PORT)" $(DOCKER_REGISTRY)/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 ROOT_PATH=$(ROOT_PATH) -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:

View File

@@ -23,6 +23,18 @@ You may customize this value with the environment variable `DEFAULT_LISTEN_PORT`
This environment variable accepts any string that can be turned into a port number.
## Root path
By default, the API root path is `/`.
You may customize this value with the environment variable `ROOT_PATH`.
This environment variable accepts a string starting and ending with `/`.
For instance, `/gotenberg/` is a valid value while `gotenberg` is not.
> This is useful if you wish to do service discovery via URL paths.
## Disable Google Chrome
In order to save some resources, the Gotenberg image accepts the environment variable `DISABLE_GOOGLE_CHROME`

View File

@@ -33,4 +33,4 @@ RUN go mod download &&\
# Copy our code source.
COPY --chown=gotenberg:gotenberg . .
CMD ["golangci-lint", "run" ,"--tests=false", "--enable-all", "--disable=dupl", "--disable=funlen" ]
CMD ["golangci-lint", "run" ,"--tests=false", "--enable-all", "--disable=dupl", "--disable=funlen", "--disable=wsl", "--disable=gocognit" ]

View File

@@ -280,6 +280,22 @@ about whats going on.</p>
<p>This environment variable accepts any string that can be turned into a port number.</p>
<h2 class="Heading"><a class="Anchor" aria-hidden="true" id="environment_variables.root_path" href="#environment_variables.root_path">
<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>Root path</h2>
<p>By default, the API root path is <code>/</code>.</p>
<p>You may customize this value with the environment variable <code>ROOT_PATH</code>.</p>
<p>This environment variable accepts a string starting and ending with <code>/</code>.</p>
<p>For instance, <code>/gotenberg/</code> is a valid value while <code>gotenberg</code> is not.</p>
<blockquote>
<p>This is useful if you wish to do service discovery via URL paths.</p>
</blockquote>
<h2 class="Heading"><a class="Anchor" aria-hidden="true" id="environment_variables.disable_google_chrome" href="#environment_variables.disable_google_chrome">
<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 Google Chrome</h2>

View File

@@ -15,31 +15,45 @@ import (
"github.com/thecodingmachine/gotenberg/internal/pkg/xtime"
)
const (
pingEndpoint string = "/ping"
mergeEndpoint string = "/merge"
convertGroupEndpoint string = "/convert"
htmlEndpoint string = "/html"
urlEndpoint string = "/url"
markdownEndpoint string = "/markdown"
officeEndpoint string = "/office"
)
func pingEndpoint(config conf.Config) string {
return fmt.Sprintf("%s%s", config.RootPath(), "ping")
}
func mergeEndpoint(config conf.Config) string {
return fmt.Sprintf("%s%s", config.RootPath(), "merge")
}
func htmlEndpoint(config conf.Config) string {
return fmt.Sprintf("%s%s", config.RootPath(), "convert/html")
}
func urlEndpoint(config conf.Config) string {
return fmt.Sprintf("%s%s", config.RootPath(), "convert/url")
}
func markdownEndpoint(config conf.Config) string {
return fmt.Sprintf("%s%s", config.RootPath(), "convert/markdown")
}
func officeEndpoint(config conf.Config) string {
return fmt.Sprintf("%s%s", config.RootPath(), "convert/office")
}
func isMultipartFormDataEndpoint(config conf.Config, path string) bool {
var multipartFormDataEndpoints []string
multipartFormDataEndpoints = append(multipartFormDataEndpoints, mergeEndpoint)
multipartFormDataEndpoints = append(multipartFormDataEndpoints, mergeEndpoint(config))
if !config.DisableGoogleChrome() {
multipartFormDataEndpoints = append(
multipartFormDataEndpoints,
fmt.Sprintf("%s%s", convertGroupEndpoint, htmlEndpoint),
fmt.Sprintf("%s%s", convertGroupEndpoint, urlEndpoint),
fmt.Sprintf("%s%s", convertGroupEndpoint, markdownEndpoint),
htmlEndpoint(config),
urlEndpoint(config),
markdownEndpoint(config),
)
}
if !config.DisableUnoconv() {
multipartFormDataEndpoints = append(
multipartFormDataEndpoints,
fmt.Sprintf("%s%s", convertGroupEndpoint, officeEndpoint),
officeEndpoint(config),
)
}
for _, endpoint := range multipartFormDataEndpoints {

View File

@@ -19,49 +19,51 @@ func TestPingHandler(t *testing.T) {
// should return 200.
config := conf.DefaultConfig()
srv := New(config)
req := httptest.NewRequest(http.MethodGet, pingEndpoint, nil)
endpoint := pingEndpoint(config)
req := httptest.NewRequest(http.MethodGet, endpoint, nil)
test.AssertStatusCode(t, http.StatusOK, srv, req)
// should return 405 as Method is wrong.
req = httptest.NewRequest(http.MethodPost, pingEndpoint, nil)
req = httptest.NewRequest(http.MethodPost, endpoint, nil)
test.AssertStatusCode(t, http.StatusMethodNotAllowed, srv, req)
}
func TestMergeHandler(t *testing.T) {
config := conf.DefaultConfig()
srv := New(config)
endpoint := mergeEndpoint(config)
// should return 200.
body, contentType := test.MergeMultipartForm(t, nil)
req := httptest.NewRequest(http.MethodPost, mergeEndpoint, body)
req := httptest.NewRequest(http.MethodPost, endpoint, body)
req.Header.Set(echo.HeaderContentType, contentType)
test.AssertStatusCode(t, http.StatusOK, srv, req)
// should return 405 as Method is wrong.
req = httptest.NewRequest(http.MethodGet, mergeEndpoint, nil)
req = httptest.NewRequest(http.MethodGet, endpoint, 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)
req = httptest.NewRequest(http.MethodPost, endpoint, body)
test.AssertStatusCode(t, http.StatusUnsupportedMediaType, srv, req)
// should return 400 as "waitTimeout" form field
// value is < 0.
body, contentType = test.MergeMultipartForm(t, map[string]string{string(resource.WaitTimeoutArgKey): "-1"})
req = httptest.NewRequest(http.MethodPost, mergeEndpoint, body)
req = httptest.NewRequest(http.MethodPost, endpoint, body)
req.Header.Set(echo.HeaderContentType, contentType)
test.AssertStatusCode(t, http.StatusBadRequest, srv, req)
// should return 400 as "waitTimeout" form field
// value is is > config.MaximumWaitTimeout().
body, contentType = test.MergeMultipartForm(t, map[string]string{string(resource.WaitTimeoutArgKey): "31"})
req = httptest.NewRequest(http.MethodPost, mergeEndpoint, body)
req = httptest.NewRequest(http.MethodPost, endpoint, body)
req.Header.Set(echo.HeaderContentType, contentType)
test.AssertStatusCode(t, http.StatusBadRequest, srv, req)
// should return 400 as "waitTimeout" form field
// value is invalid.
body, contentType = test.MergeMultipartForm(t, map[string]string{string(resource.WaitTimeoutArgKey): "not a float"})
req = httptest.NewRequest(http.MethodPost, mergeEndpoint, body)
req = httptest.NewRequest(http.MethodPost, endpoint, body)
req.Header.Set(echo.HeaderContentType, contentType)
test.AssertStatusCode(t, http.StatusBadRequest, srv, req)
// should return 504.
body, contentType = test.MergeMultipartForm(t, map[string]string{string(resource.WaitTimeoutArgKey): "0"})
req = httptest.NewRequest(http.MethodPost, mergeEndpoint, body)
req = httptest.NewRequest(http.MethodPost, endpoint, body)
req.Header.Set(echo.HeaderContentType, contentType)
test.AssertStatusCode(t, http.StatusGatewayTimeout, srv, req)
}
@@ -69,7 +71,7 @@ func TestMergeHandler(t *testing.T) {
func TestHTMLHandler(t *testing.T) {
config := conf.DefaultConfig()
srv := New(config)
endpoint := fmt.Sprintf("%s%s", convertGroupEndpoint, htmlEndpoint)
endpoint := htmlEndpoint(config)
// should return 200.
body, contentType := test.HTMLMultipartForm(t, nil)
req := httptest.NewRequest(http.MethodPost, endpoint, body)
@@ -224,7 +226,7 @@ func TestHTMLHandler(t *testing.T) {
func TestURLHandler(t *testing.T) {
config := conf.DefaultConfig()
srv := New(config)
endpoint := fmt.Sprintf("%s%s", convertGroupEndpoint, urlEndpoint)
endpoint := urlEndpoint(config)
// should return 200.
body, contentType := test.URLMultipartForm(t, nil)
req := httptest.NewRequest(http.MethodPost, endpoint, body)
@@ -379,7 +381,7 @@ func TestURLHandler(t *testing.T) {
func TestMarkdownHandler(t *testing.T) {
config := conf.DefaultConfig()
srv := New(config)
endpoint := fmt.Sprintf("%s%s", convertGroupEndpoint, markdownEndpoint)
endpoint := markdownEndpoint(config)
// should return 200.
body, contentType := test.MarkdownMultipartForm(t, nil)
req := httptest.NewRequest(http.MethodPost, endpoint, body)
@@ -534,7 +536,7 @@ func TestMarkdownHandler(t *testing.T) {
func TestOfficeHandler(t *testing.T) {
config := conf.DefaultConfig()
srv := New(config)
endpoint := fmt.Sprintf("%s%s", convertGroupEndpoint, officeEndpoint)
endpoint := officeEndpoint(config)
// should return 200.
body, contentType := test.OfficeMultipartForm(t, nil)
req := httptest.NewRequest(http.MethodPost, endpoint, body)
@@ -605,7 +607,7 @@ func TestWebhook(t *testing.T) {
srv := New(config)
// our custom server should receive the PDF.
body, contentType := test.MergeMultipartForm(t, map[string]string{string(resource.WebhookURLArgKey): "http://localhost:3001/foo"})
req := httptest.NewRequest(http.MethodPost, mergeEndpoint, body)
req := httptest.NewRequest(http.MethodPost, mergeEndpoint(config), body)
req.Header.Set(echo.HeaderContentType, contentType)
test.AssertStatusCode(t, http.StatusOK, srv, req)
err := <-status
@@ -616,7 +618,7 @@ func TestResultFilename(t *testing.T) {
config := conf.DefaultConfig()
srv := New(config)
body, contentType := test.MergeMultipartForm(t, map[string]string{string(resource.ResultFilenameArgKey): "foo.pdf"})
req := httptest.NewRequest(http.MethodPost, mergeEndpoint, body)
req := httptest.NewRequest(http.MethodPost, mergeEndpoint(config), body)
req.Header.Set(echo.HeaderContentType, contentType)
rec := httptest.NewRecorder()
srv.ServeHTTP(rec, req)

View File

@@ -30,7 +30,7 @@ func contextMiddleware(config conf.Config) echo.MiddlewareFunc {
// there is no need to create a Resource.
if !isMultipartFormDataEndpoint(config, ctx.Path()) {
// validate method for healthcheck endpoint.
if ctx.Path() == pingEndpoint && ctx.Request().Method != http.MethodGet {
if ctx.Path() == pingEndpoint(config) && ctx.Request().Method != http.MethodGet {
err := doErr(ctx, echo.NewHTTPError(http.StatusMethodNotAllowed))
return ctx.LogRequestResult(err, false)
}
@@ -60,14 +60,14 @@ func contextMiddleware(config conf.Config) echo.MiddlewareFunc {
}
// loggerMiddleware logs the result of a request.
func loggerMiddleware() echo.MiddlewareFunc {
func loggerMiddleware(config conf.Config) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
ctx := context.MustCastFromEchoContext(c)
err := next(ctx)
// we do not want to log healthcheck requests if
// log level is not set to DEBUG.
isDebug := ctx.Path() == pingEndpoint
isDebug := ctx.Path() == pingEndpoint(config)
return ctx.LogRequestResult(err, isDebug)
}
}

View File

@@ -11,22 +11,21 @@ func New(config conf.Config) *echo.Echo {
srv.HideBanner = true
srv.HidePort = true
srv.Use(contextMiddleware(config))
srv.Use(loggerMiddleware())
srv.Use(loggerMiddleware(config))
srv.Use(cleanupMiddleware())
srv.Use(errorMiddleware())
srv.GET(pingEndpoint, pingHandler)
srv.POST(mergeEndpoint, mergeHandler)
srv.GET(pingEndpoint(config), pingHandler)
srv.POST(mergeEndpoint(config), mergeHandler)
if config.DisableGoogleChrome() && config.DisableUnoconv() {
return srv
}
g := srv.Group(convertGroupEndpoint)
if !config.DisableGoogleChrome() {
g.POST(htmlEndpoint, htmlHandler)
g.POST(urlEndpoint, urlHandler)
g.POST(markdownEndpoint, markdownHandler)
srv.POST(htmlEndpoint(config), htmlHandler)
srv.POST(urlEndpoint(config), urlHandler)
srv.POST(markdownEndpoint(config), markdownHandler)
}
if !config.DisableUnoconv() {
g.POST(officeEndpoint, officeHandler)
srv.POST(officeEndpoint(config), officeHandler)
}
return srv
}

View File

@@ -1,7 +1,6 @@
package xhttp
import (
"fmt"
"net/http"
"net/http/httptest"
"os"
@@ -28,31 +27,31 @@ func TestDisableChromeEndpoints(t *testing.T) {
assert.Nil(t, err)
srv := New(config)
// Ping endpoint should return 200.
req := httptest.NewRequest(http.MethodGet, pingEndpoint, nil)
req := httptest.NewRequest(http.MethodGet, pingEndpoint(config), nil)
test.AssertStatusCode(t, http.StatusOK, srv, req)
// Merge endpoint should return 200.
body, contentType := test.MergeMultipartForm(t, nil)
req = httptest.NewRequest(http.MethodPost, mergeEndpoint, body)
req = httptest.NewRequest(http.MethodPost, mergeEndpoint(config), body)
req.Header.Set(echo.HeaderContentType, contentType)
test.AssertStatusCode(t, http.StatusOK, srv, req)
// HTML endpoint should return 404.
body, contentType = test.HTMLMultipartForm(t, nil)
req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, htmlEndpoint), body)
req = httptest.NewRequest(http.MethodPost, htmlEndpoint(config), body)
req.Header.Set(echo.HeaderContentType, contentType)
test.AssertStatusCode(t, http.StatusNotFound, srv, req)
// URL endpoint should return 404.
body, contentType = test.URLMultipartForm(t, nil)
req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, urlEndpoint), body)
req = httptest.NewRequest(http.MethodPost, urlEndpoint(config), body)
req.Header.Set(echo.HeaderContentType, contentType)
test.AssertStatusCode(t, http.StatusNotFound, srv, req)
// Markdown endpoint should return 404.
body, contentType = test.MarkdownMultipartForm(t, nil)
req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, markdownEndpoint), body)
req = httptest.NewRequest(http.MethodPost, markdownEndpoint(config), body)
req.Header.Set(echo.HeaderContentType, contentType)
test.AssertStatusCode(t, http.StatusNotFound, srv, req)
// Office endpoint should return 200.
body, contentType = test.OfficeMultipartForm(t, nil)
req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, officeEndpoint), body)
req = httptest.NewRequest(http.MethodPost, officeEndpoint(config), body)
req.Header.Set(echo.HeaderContentType, contentType)
test.AssertStatusCode(t, http.StatusOK, srv, req)
// finally...
@@ -65,31 +64,31 @@ func TestDisableUnoconvEndpoints(t *testing.T) {
assert.Nil(t, err)
srv := New(config)
// Ping endpoint should return 200.
req := httptest.NewRequest(http.MethodGet, pingEndpoint, nil)
req := httptest.NewRequest(http.MethodGet, pingEndpoint(config), nil)
test.AssertStatusCode(t, http.StatusOK, srv, req)
// Merge endpoint should return 200.
body, contentType := test.MergeMultipartForm(t, nil)
req = httptest.NewRequest(http.MethodPost, mergeEndpoint, body)
req = httptest.NewRequest(http.MethodPost, mergeEndpoint(config), body)
req.Header.Set(echo.HeaderContentType, contentType)
test.AssertStatusCode(t, http.StatusOK, srv, req)
// HTML endpoint should return 200.
body, contentType = test.HTMLMultipartForm(t, nil)
req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, htmlEndpoint), body)
req = httptest.NewRequest(http.MethodPost, htmlEndpoint(config), body)
req.Header.Set(echo.HeaderContentType, contentType)
test.AssertStatusCode(t, http.StatusOK, srv, req)
// URL endpoint should return 200.
body, contentType = test.URLMultipartForm(t, nil)
req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, urlEndpoint), body)
req = httptest.NewRequest(http.MethodPost, urlEndpoint(config), body)
req.Header.Set(echo.HeaderContentType, contentType)
test.AssertStatusCode(t, http.StatusOK, srv, req)
// Markdown endpoint should return 200.
body, contentType = test.MarkdownMultipartForm(t, nil)
req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, markdownEndpoint), body)
req = httptest.NewRequest(http.MethodPost, markdownEndpoint(config), body)
req.Header.Set(echo.HeaderContentType, contentType)
test.AssertStatusCode(t, http.StatusOK, srv, req)
// Office endpoint should return 404.
body, contentType = test.OfficeMultipartForm(t, nil)
req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, officeEndpoint), body)
req = httptest.NewRequest(http.MethodPost, officeEndpoint(config), body)
req.Header.Set(echo.HeaderContentType, contentType)
test.AssertStatusCode(t, http.StatusNotFound, srv, req)
// finally...
@@ -102,34 +101,71 @@ func TestDisableChromeAndUnoconvEndpoints(t *testing.T) {
assert.Nil(t, err)
srv := New(config)
// Ping endpoint should return 200.
req := httptest.NewRequest(http.MethodGet, pingEndpoint, nil)
req := httptest.NewRequest(http.MethodGet, pingEndpoint(config), nil)
test.AssertStatusCode(t, http.StatusOK, srv, req)
// Merge endpoint should return 200.
body, contentType := test.MergeMultipartForm(t, nil)
req = httptest.NewRequest(http.MethodPost, mergeEndpoint, body)
req = httptest.NewRequest(http.MethodPost, mergeEndpoint(config), body)
req.Header.Set(echo.HeaderContentType, contentType)
test.AssertStatusCode(t, http.StatusOK, srv, req)
// HTML endpoint should return 404.
body, contentType = test.HTMLMultipartForm(t, nil)
req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, htmlEndpoint), body)
req = httptest.NewRequest(http.MethodPost, htmlEndpoint(config), body)
req.Header.Set(echo.HeaderContentType, contentType)
test.AssertStatusCode(t, http.StatusNotFound, srv, req)
// URL endpoint should return 404.
body, contentType = test.URLMultipartForm(t, nil)
req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, urlEndpoint), body)
req = httptest.NewRequest(http.MethodPost, urlEndpoint(config), body)
req.Header.Set(echo.HeaderContentType, contentType)
test.AssertStatusCode(t, http.StatusNotFound, srv, req)
// Markdown endpoint should return 404.
body, contentType = test.MarkdownMultipartForm(t, nil)
req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, markdownEndpoint), body)
req = httptest.NewRequest(http.MethodPost, markdownEndpoint(config), body)
req.Header.Set(echo.HeaderContentType, contentType)
test.AssertStatusCode(t, http.StatusNotFound, srv, req)
// Office endpoint should return 404.
body, contentType = test.OfficeMultipartForm(t, nil)
req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, officeEndpoint), body)
req = httptest.NewRequest(http.MethodPost, officeEndpoint(config), body)
req.Header.Set(echo.HeaderContentType, contentType)
test.AssertStatusCode(t, http.StatusNotFound, srv, req)
// finally...
os.Setenv(conf.DisableGoogleChromeEnvVar, "0")
os.Setenv(conf.DisableUnoconvEnvVar, "0")
}
func TestCustomRootPath(t *testing.T) {
os.Setenv(conf.RootPathEnvVar, "/foo/")
config, err := conf.FromEnv()
assert.Nil(t, err)
srv := New(config)
// Ping endpoint should return 200.
req := httptest.NewRequest(http.MethodGet, pingEndpoint(config), nil)
test.AssertStatusCode(t, http.StatusOK, srv, req)
// Merge endpoint should return 200.
body, contentType := test.MergeMultipartForm(t, nil)
req = httptest.NewRequest(http.MethodPost, mergeEndpoint(config), body)
req.Header.Set(echo.HeaderContentType, contentType)
test.AssertStatusCode(t, http.StatusOK, srv, req)
// HTML endpoint should return 200.
body, contentType = test.HTMLMultipartForm(t, nil)
req = httptest.NewRequest(http.MethodPost, htmlEndpoint(config), body)
req.Header.Set(echo.HeaderContentType, contentType)
test.AssertStatusCode(t, http.StatusOK, srv, req)
// URL endpoint should return 200.
body, contentType = test.URLMultipartForm(t, nil)
req = httptest.NewRequest(http.MethodPost, urlEndpoint(config), body)
req.Header.Set(echo.HeaderContentType, contentType)
test.AssertStatusCode(t, http.StatusOK, srv, req)
// Markdown endpoint should return 200.
body, contentType = test.MarkdownMultipartForm(t, nil)
req = httptest.NewRequest(http.MethodPost, markdownEndpoint(config), body)
req.Header.Set(echo.HeaderContentType, contentType)
test.AssertStatusCode(t, http.StatusOK, srv, req)
// Office endpoint should return 200.
body, contentType = test.OfficeMultipartForm(t, nil)
req = httptest.NewRequest(http.MethodPost, officeEndpoint(config), body)
req.Header.Set(echo.HeaderContentType, contentType)
test.AssertStatusCode(t, http.StatusOK, srv, req)
// finally...
os.Setenv(conf.RootPathEnvVar, "/")
}

View File

@@ -34,6 +34,9 @@ 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"
@@ -51,6 +54,7 @@ type Config struct {
disableGoogleChrome bool
disableUnoconv bool
logLevel xlog.Level
rootPath string
maximumGoogleChromeRpccBufferSize int64
defaultGoogleChromeRpccBufferSize int64
}
@@ -68,6 +72,7 @@ func DefaultConfig() Config {
disableGoogleChrome: false,
disableUnoconv: false,
logLevel: xlog.InfoLevel,
rootPath: "/",
maximumGoogleChromeRpccBufferSize: 104857600, // ~100 MB
defaultGoogleChromeRpccBufferSize: 1048576, // 1 MB
}
@@ -163,6 +168,16 @@ 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,
@@ -242,6 +257,12 @@ 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 {

View File

@@ -320,6 +320,29 @@ 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
@@ -368,6 +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 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)
}