mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-16 04:12:16 +01:00
huge refactoring
This commit is contained in:
3
internal/app/xhttp/doc.go
Normal file
3
internal/app/xhttp/doc.go
Normal file
@@ -0,0 +1,3 @@
|
||||
// Package xhttp defines our own implementation
|
||||
// of echo.Echo.
|
||||
package xhttp
|
||||
298
internal/app/xhttp/handler.go
Normal file
298
internal/app/xhttp/handler.go
Normal file
@@ -0,0 +1,298 @@
|
||||
package xhttp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/thecodingmachine/gotenberg/internal/app/xhttp/pkg/context"
|
||||
"github.com/thecodingmachine/gotenberg/internal/app/xhttp/pkg/resource"
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/printer"
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/xerror"
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/xrand"
|
||||
)
|
||||
|
||||
const (
|
||||
pingEndpoint string = "/ping"
|
||||
mergeEndpoint string = "/merge"
|
||||
convertGroupEndpoint string = "/convert"
|
||||
htmlEndpoint string = "/html"
|
||||
urlEndpoint string = "/url"
|
||||
markdownEndpoint string = "/markdown"
|
||||
officeEndpoint string = "/office"
|
||||
)
|
||||
|
||||
// pingHandler is the handler for healthcheck.
|
||||
func pingHandler(c echo.Context) error {
|
||||
const op string = "xhttp.pingHandler"
|
||||
ctx := context.MustCastFromEchoContext(c)
|
||||
ctx.XLogger().DebugOp(op, "handling ping request...")
|
||||
if err := ctx.ProcessesHealthcheck(); err != nil {
|
||||
return xerror.New(op, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// mergeHandler is the handler for merging
|
||||
// PDF files.
|
||||
func mergeHandler(c echo.Context) error {
|
||||
const op string = "xhttp.mergeHandler"
|
||||
resolver := func() error {
|
||||
ctx := context.MustCastFromEchoContext(c)
|
||||
logger := ctx.XLogger()
|
||||
logger.DebugOp(op, "handling merge request...")
|
||||
r := ctx.MustResource()
|
||||
opts, err := mergePrinterOptions(r, ctx.Config())
|
||||
if err != nil {
|
||||
return xerror.New(op, err)
|
||||
}
|
||||
fpaths, err := r.Fpaths(".pdf")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p := printer.NewMergePrinter(logger, fpaths, opts)
|
||||
return convert(ctx, p)
|
||||
}
|
||||
if err := resolver(); err != nil {
|
||||
return xerror.New(op, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// htmlHandler is the handler for converting
|
||||
// HTML to PDF.
|
||||
func htmlHandler(c echo.Context) error {
|
||||
const op string = "xhttp.htmlHandler"
|
||||
resolver := func() error {
|
||||
ctx := context.MustCastFromEchoContext(c)
|
||||
logger := ctx.XLogger()
|
||||
logger.DebugOp(op, "handling HTML request...")
|
||||
r := ctx.MustResource()
|
||||
opts, err := chromePrinterOptions(r, ctx.Config())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fpath, err := r.Fpath("index.html")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p := printer.NewHTMLPrinter(logger, fpath, opts)
|
||||
return convert(ctx, p)
|
||||
}
|
||||
if err := resolver(); err != nil {
|
||||
return xerror.New(op, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// urlHandler is the handler for converting
|
||||
// a URL to PDF.
|
||||
func urlHandler(c echo.Context) error {
|
||||
const op string = "xhttp.urlHandler"
|
||||
resolver := func() error {
|
||||
ctx := context.MustCastFromEchoContext(c)
|
||||
logger := ctx.XLogger()
|
||||
logger.DebugOp(op, "handling URL request...")
|
||||
r := ctx.MustResource()
|
||||
opts, err := chromePrinterOptions(r, ctx.Config())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !r.HasArg(resource.RemoteURLArgKey) {
|
||||
return xerror.Invalid(
|
||||
op,
|
||||
fmt.Sprintf("'%s' not found or empty", resource.RemoteURLArgKey),
|
||||
nil,
|
||||
)
|
||||
}
|
||||
remoteURL, err := r.StringArg(resource.RemoteURLArgKey, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p := printer.NewURLPrinter(logger, remoteURL, opts)
|
||||
return convert(ctx, p)
|
||||
}
|
||||
if err := resolver(); err != nil {
|
||||
return xerror.New(op, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// markdownHandler is the handler for converting
|
||||
// Markdown to PDF.
|
||||
func markdownHandler(c echo.Context) error {
|
||||
const op string = "xhttp.markdownHandler"
|
||||
resolver := func() error {
|
||||
ctx := context.MustCastFromEchoContext(c)
|
||||
logger := ctx.XLogger()
|
||||
logger.DebugOp(op, "handling Markdown request...")
|
||||
r := ctx.MustResource()
|
||||
opts, err := chromePrinterOptions(r, ctx.Config())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fpath, err := r.Fpath("index.html")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p, err := printer.NewMarkdownPrinter(logger, fpath, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return convert(ctx, p)
|
||||
}
|
||||
if err := resolver(); err != nil {
|
||||
return xerror.New(op, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// officeHandler is the handler for converting
|
||||
// Office documents to PDF.
|
||||
func officeHandler(c echo.Context) error {
|
||||
const op string = "xhttp.officeHandler"
|
||||
resolver := func() error {
|
||||
ctx := context.MustCastFromEchoContext(c)
|
||||
logger := ctx.XLogger()
|
||||
logger.DebugOp(op, "handling Office request...")
|
||||
r := ctx.MustResource()
|
||||
opts, err := officePrinterOptions(r, ctx.Config())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fpaths, err := r.Fpaths(
|
||||
".txt",
|
||||
".rtf",
|
||||
".fodt",
|
||||
".doc",
|
||||
".docx",
|
||||
".odt",
|
||||
".xls",
|
||||
".xlsx",
|
||||
".ods",
|
||||
".ppt",
|
||||
".pptx",
|
||||
".odp",
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p := printer.NewOfficePrinter(logger, fpaths, opts)
|
||||
return convert(ctx, p)
|
||||
}
|
||||
if err := resolver(); err != nil {
|
||||
return xerror.New(op, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func convert(ctx context.Context, p printer.Printer) error {
|
||||
const op string = "xhttp.convert"
|
||||
resolver := func() error {
|
||||
logger := ctx.XLogger()
|
||||
r := ctx.MustResource()
|
||||
baseFilename := xrand.Get()
|
||||
filename := fmt.Sprintf("%s.pdf", baseFilename)
|
||||
fpath := fmt.Sprintf("%s/%s", r.DirPath(), filename)
|
||||
// if no webhook URL given, run conversion
|
||||
// and directly return the resulting PDF file
|
||||
// or an error.
|
||||
if !r.HasArg(resource.WebhookURLArgKey) {
|
||||
logger.DebugfOp(op, "no '%s' found, converting synchronously", resource.WebhookURLArgKey)
|
||||
return convertSync(ctx, p, filename, fpath)
|
||||
}
|
||||
// as a webhook URL has been given, we
|
||||
// run the following lines in a goroutine so that
|
||||
// it doesn't block.
|
||||
logger.DebugfOp(op, "'%s' found, converting asynchronously", resource.WebhookURLArgKey)
|
||||
return convertAsync(ctx, p, filename, fpath)
|
||||
}
|
||||
if err := resolver(); err != nil {
|
||||
return xerror.New(op, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertSync(ctx context.Context, p printer.Printer, filename, fpath string) error {
|
||||
const op = "xhttp.convertSync"
|
||||
resolver := func() error {
|
||||
logger := ctx.XLogger()
|
||||
r := ctx.MustResource()
|
||||
|
||||
if err := p.Print(fpath); err != nil {
|
||||
return err
|
||||
}
|
||||
if !r.HasArg(resource.ResultFilenameArgKey) {
|
||||
logger.DebugfOp(
|
||||
op,
|
||||
"no '%s' found, using generated filename '%s'",
|
||||
resource.RemoteURLArgKey,
|
||||
filename,
|
||||
)
|
||||
if err := ctx.Attachment(fpath, filename); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
logger.DebugfOp(
|
||||
op,
|
||||
"'%s' found, so not using generated filename",
|
||||
resource.ResultFilenameArgKey,
|
||||
)
|
||||
filename, err := r.StringArg(resource.ResultFilenameArgKey, filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ctx.Attachment(fpath, filename); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if err := resolver(); err != nil {
|
||||
return xerror.New(op, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertAsync(ctx context.Context, p printer.Printer, filename, fpath string) error {
|
||||
const op = "xhttp.convertAsync"
|
||||
logger := ctx.XLogger()
|
||||
r := ctx.MustResource()
|
||||
go func() {
|
||||
defer r.Close() // nolint: errcheck
|
||||
if err := p.Print(fpath); err != nil {
|
||||
xerr := xerror.New(op, err)
|
||||
logger.ErrorOp(xerror.Op(xerr), xerr)
|
||||
return
|
||||
}
|
||||
f, err := os.Open(fpath)
|
||||
if err != nil {
|
||||
xerr := xerror.New(op, err)
|
||||
logger.ErrorOp(xerror.Op(xerr), xerr)
|
||||
return
|
||||
}
|
||||
defer f.Close() // nolint: errcheck
|
||||
webhookURL, err := r.StringArg(resource.WebhookURLArgKey, "")
|
||||
if err != nil {
|
||||
xerr := xerror.New(op, err)
|
||||
logger.ErrorOp(xerror.Op(xerr), xerr)
|
||||
return
|
||||
}
|
||||
logger.DebugfOp(
|
||||
op,
|
||||
"sending result file '%s' to '%s'",
|
||||
filename,
|
||||
webhookURL,
|
||||
)
|
||||
// TODO timeout
|
||||
resp, err := http.Post(webhookURL, "application/pdf", f) /* #nosec */
|
||||
if err != nil {
|
||||
xerr := xerror.New(op, err)
|
||||
logger.ErrorOp(xerror.Op(xerr), xerr)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close() // nolint: errcheck
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
128
internal/app/xhttp/middleware.go
Normal file
128
internal/app/xhttp/middleware.go
Normal file
@@ -0,0 +1,128 @@
|
||||
package xhttp
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/thecodingmachine/gotenberg/internal/app/xhttp/pkg/context"
|
||||
"github.com/thecodingmachine/gotenberg/internal/app/xhttp/pkg/resource"
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/conf"
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/pm2"
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/xerror"
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/xlog"
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/xrand"
|
||||
)
|
||||
|
||||
// contextMiddleware extends the default echo.Context with
|
||||
// our custom context.Context.
|
||||
func contextMiddleware(config conf.Config, processes ...pm2.Process) echo.MiddlewareFunc {
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
// generate a unique identifier for the request.
|
||||
trace := xrand.Get()
|
||||
// create the logger for this request using
|
||||
// the previous identifier as trace.
|
||||
logger := xlog.New(config.LogLevel(), trace)
|
||||
// extend the current echo context with our custom
|
||||
// context.
|
||||
ctx := context.New(c, logger, config, processes...)
|
||||
// if its an healthcheck request, there
|
||||
// is no need to create a Resource.
|
||||
if ctx.Path() == pingEndpoint {
|
||||
return next(ctx)
|
||||
}
|
||||
// if the endpoint is not for healthcheck, create a
|
||||
// Resource.
|
||||
if err := ctx.WithResource(trace); err != nil {
|
||||
// required to have a correct status code.
|
||||
ctx.Error(err)
|
||||
return ctx.LogRequestResult(err, false)
|
||||
}
|
||||
return next(ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// loggerMiddleware logs the result of a request.
|
||||
func loggerMiddleware() 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
|
||||
return ctx.LogRequestResult(err, isDebug)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// cleanupMiddleware removes a resource.Resource
|
||||
// at the end of a request.
|
||||
func cleanupMiddleware() echo.MiddlewareFunc {
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
const op string = "xhttp.cleanupMiddleware"
|
||||
err := next(c)
|
||||
ctx := context.MustCastFromEchoContext(c)
|
||||
if !ctx.HasResource() {
|
||||
// nothing to remove.
|
||||
return err
|
||||
}
|
||||
r := ctx.MustResource()
|
||||
// if a webhook URL has been given,
|
||||
// do not remove the resource.Resource here because
|
||||
// we don't know if the result file has been
|
||||
// generated or sent.
|
||||
if r.HasArg(resource.WebhookURLArgKey) {
|
||||
return err
|
||||
}
|
||||
// a resource.Resource is associated with our custom context.
|
||||
if resourceErr := r.Close(); resourceErr != nil {
|
||||
xerr := xerror.New(op, resourceErr)
|
||||
ctx.XLogger().ErrorOp(xerror.Op(xerr), xerr)
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// errorMiddleware handles errors (if any).
|
||||
func errorMiddleware() echo.MiddlewareFunc {
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
ctx := context.MustCastFromEchoContext(c)
|
||||
err := next(ctx)
|
||||
if err == nil {
|
||||
// so far so good!
|
||||
return nil
|
||||
}
|
||||
// if it's an error from echo
|
||||
// like 404 not found and so on.
|
||||
if echoHTTPErr, ok := err.(*echo.HTTPError); ok {
|
||||
return echoHTTPErr
|
||||
}
|
||||
// we log the initial error before returning
|
||||
// the HTTP error.
|
||||
errOp := xerror.Op(err)
|
||||
logger := ctx.XLogger()
|
||||
logger.ErrorOp(errOp, err)
|
||||
// handle our custom HTTP error.
|
||||
var httpErr error
|
||||
errCode := xerror.Code(err)
|
||||
errMessage := xerror.Message(err)
|
||||
switch errCode {
|
||||
case xerror.InvalidCode:
|
||||
httpErr = echo.NewHTTPError(http.StatusBadRequest, errMessage)
|
||||
case xerror.TimeoutCode:
|
||||
// TODO status
|
||||
httpErr = echo.NewHTTPError(http.StatusBadGateway, errMessage)
|
||||
default:
|
||||
httpErr = echo.NewHTTPError(http.StatusInternalServerError, errMessage)
|
||||
}
|
||||
// required to have a correct status code.
|
||||
ctx.Error(httpErr)
|
||||
return httpErr
|
||||
}
|
||||
}
|
||||
}
|
||||
93
internal/app/xhttp/option.go
Normal file
93
internal/app/xhttp/option.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package xhttp
|
||||
|
||||
import (
|
||||
"github.com/thecodingmachine/gotenberg/internal/app/xhttp/pkg/resource"
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/conf"
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/printer"
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/xerror"
|
||||
)
|
||||
|
||||
func mergePrinterOptions(r resource.Resource, config conf.Config) (printer.MergePrinterOptions, error) {
|
||||
const op string = "xhttp.mergePrinterOptions"
|
||||
waitTimeout, err := resource.WaitTimeoutArg(r, config)
|
||||
if err != nil {
|
||||
return printer.MergePrinterOptions{}, xerror.New(op, err)
|
||||
}
|
||||
return printer.MergePrinterOptions{
|
||||
WaitTimeout: waitTimeout,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func chromePrinterOptions(r resource.Resource, config conf.Config) (printer.ChromePrinterOptions, error) {
|
||||
const op string = "xhttp.chromePrinterOptions"
|
||||
resolver := func() (printer.ChromePrinterOptions, error) {
|
||||
waitTimeout, err := resource.WaitTimeoutArg(r, config)
|
||||
if err != nil {
|
||||
return printer.ChromePrinterOptions{}, err
|
||||
}
|
||||
waitDelay, err := resource.WaitDelayArg(r, config)
|
||||
if err != nil {
|
||||
return printer.ChromePrinterOptions{}, err
|
||||
}
|
||||
headerHTML, footerHTML,
|
||||
err := resource.HeaderFooterContents(r)
|
||||
if err != nil {
|
||||
return printer.ChromePrinterOptions{}, err
|
||||
}
|
||||
paperWidth, paperHeight,
|
||||
err := resource.PaperSizeArgs(r)
|
||||
if err != nil {
|
||||
return printer.ChromePrinterOptions{}, err
|
||||
}
|
||||
marginTop, marginBottom, marginLeft, marginRight,
|
||||
err := resource.MarginArgs(r)
|
||||
if err != nil {
|
||||
return printer.ChromePrinterOptions{}, err
|
||||
}
|
||||
landscape, err := r.BoolArg(resource.LandscapeArgKey, false)
|
||||
if err != nil {
|
||||
return printer.ChromePrinterOptions{}, err
|
||||
}
|
||||
return printer.ChromePrinterOptions{
|
||||
WaitTimeout: waitTimeout,
|
||||
WaitDelay: waitDelay,
|
||||
HeaderHTML: headerHTML,
|
||||
FooterHTML: footerHTML,
|
||||
PaperWidth: paperWidth,
|
||||
PaperHeight: paperHeight,
|
||||
MarginTop: marginTop,
|
||||
MarginBottom: marginBottom,
|
||||
MarginLeft: marginLeft,
|
||||
MarginRight: marginRight,
|
||||
Landscape: landscape,
|
||||
}, nil
|
||||
}
|
||||
opts, err := resolver()
|
||||
if err != nil {
|
||||
return opts, xerror.New(op, err)
|
||||
}
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
func officePrinterOptions(r resource.Resource, config conf.Config) (printer.OfficePrinterOptions, error) {
|
||||
const op string = "xhttp.officePrinterOptions"
|
||||
resolver := func() (printer.OfficePrinterOptions, error) {
|
||||
waitTimeout, err := resource.WaitTimeoutArg(r, config)
|
||||
if err != nil {
|
||||
return printer.OfficePrinterOptions{}, err
|
||||
}
|
||||
landscape, err := r.BoolArg(resource.LandscapeArgKey, false)
|
||||
if err != nil {
|
||||
return printer.OfficePrinterOptions{}, err
|
||||
}
|
||||
return printer.OfficePrinterOptions{
|
||||
WaitTimeout: waitTimeout,
|
||||
Landscape: landscape,
|
||||
}, nil
|
||||
}
|
||||
opts, err := resolver()
|
||||
if err != nil {
|
||||
return opts, xerror.New(op, err)
|
||||
}
|
||||
return opts, nil
|
||||
}
|
||||
210
internal/app/xhttp/pkg/context/context.go
Normal file
210
internal/app/xhttp/pkg/context/context.go
Normal file
@@ -0,0 +1,210 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/thecodingmachine/gotenberg/internal/app/xhttp/pkg/resource"
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/conf"
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/pm2"
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/xerror"
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/xlog"
|
||||
)
|
||||
|
||||
// Context extends the default echo.Context.
|
||||
type Context struct {
|
||||
echo.Context
|
||||
logger xlog.Logger
|
||||
config conf.Config
|
||||
processes []pm2.Process
|
||||
resource resource.Resource
|
||||
startTime time.Time
|
||||
}
|
||||
|
||||
// New creates a new Context.
|
||||
func New(c echo.Context, logger xlog.Logger, config conf.Config, processess ...pm2.Process) Context {
|
||||
return Context{
|
||||
c,
|
||||
logger,
|
||||
config,
|
||||
processess,
|
||||
resource.Resource{},
|
||||
time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
MustCastFromEchoContext cast an echo.Context
|
||||
to our custom Context.
|
||||
|
||||
It panics if casting goes wrong.
|
||||
*/
|
||||
func MustCastFromEchoContext(c echo.Context) Context {
|
||||
const op string = "context.MustCastFromEchoContext"
|
||||
ctx, ok := c.(Context)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("%s: unable to cast an echo.Context to our custom context.Context", op))
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
/*
|
||||
XLogger returns the xlog.Logger associated
|
||||
with the Context.
|
||||
|
||||
This method should be used instead of the
|
||||
default Logger() method coming from
|
||||
the echo.Context.
|
||||
*/
|
||||
func (ctx Context) XLogger() xlog.Logger {
|
||||
return ctx.logger
|
||||
}
|
||||
|
||||
// Config returns the conf.Config associated
|
||||
// with the Context.
|
||||
func (ctx Context) Config() conf.Config {
|
||||
return ctx.config
|
||||
}
|
||||
|
||||
// ProcessesHealthcheck returns an error if
|
||||
// one of the processes is not viable.
|
||||
func (ctx Context) ProcessesHealthcheck() error {
|
||||
const op string = "context.Context.ProcessesHealthcheck"
|
||||
for _, process := range ctx.processes {
|
||||
if !process.IsViable() {
|
||||
return xerror.New(
|
||||
op,
|
||||
fmt.Errorf("'%s' is not viable", process.Fullname()),
|
||||
)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithResource creates a resource.Resource and
|
||||
// adds it to the Context.
|
||||
func (ctx *Context) WithResource(directoryName string) error {
|
||||
const op string = "context.Context.WithResource"
|
||||
resolver := func() (resource.Resource, error) {
|
||||
r, err := resource.New(ctx.logger, directoryName)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
// retrieve form values from request.
|
||||
for _, key := range resource.ArgKeys() {
|
||||
r.WithArg(key, ctx.FormValue(string(key)))
|
||||
}
|
||||
// write form files from request.
|
||||
form, err := ctx.MultipartForm()
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
for _, files := range form.File {
|
||||
for _, fh := range files {
|
||||
in, err := fh.Open()
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
defer in.Close() // nolint: errcheck
|
||||
if err := r.WithFile(fh.Filename, in); err != nil {
|
||||
return r, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
resource, err := resolver()
|
||||
ctx.resource = resource
|
||||
if err != nil {
|
||||
return xerror.New(op, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
MustResource returns the resource.Resource
|
||||
associated with the Context.
|
||||
|
||||
It panics if no resource.Resource.
|
||||
*/
|
||||
func (ctx Context) MustResource() resource.Resource {
|
||||
const op string = "context.Context.MustResource"
|
||||
if !ctx.HasResource() {
|
||||
panic(fmt.Sprintf("%s: unable to retrieve the resource.Resource from our custom context.Context", op))
|
||||
}
|
||||
return ctx.resource
|
||||
}
|
||||
|
||||
// HasResource returns true if the Context
|
||||
// has a resource.Resource.
|
||||
func (ctx Context) HasResource() bool {
|
||||
return &ctx.resource != nil
|
||||
}
|
||||
|
||||
/*
|
||||
LogRequestResult logs the result of a request.
|
||||
This method should only be used by a middleware!
|
||||
|
||||
If an error is given, returns the exact same error.
|
||||
*/
|
||||
func (ctx Context) LogRequestResult(err error, isDebug bool) error {
|
||||
const op string = "context.Context.LogRequestResult"
|
||||
req := ctx.Request()
|
||||
resp := ctx.Response()
|
||||
stopTime := time.Now()
|
||||
fields := map[string]interface{}{
|
||||
"remote_ip": ctx.RealIP(),
|
||||
"host": req.Host,
|
||||
"uri": req.RequestURI,
|
||||
"method": req.Method,
|
||||
"path": path(req),
|
||||
"referer": req.Referer(),
|
||||
"user_agent": req.UserAgent(),
|
||||
"status": resp.Status,
|
||||
"latency": lantency(ctx.startTime, stopTime),
|
||||
"latency_human": latencyHuman(ctx.startTime, stopTime),
|
||||
"bytes_in": bytesIn(req),
|
||||
"bytes_out": bytesOut(resp),
|
||||
}
|
||||
if err != nil {
|
||||
ctx.logger.WithFields(fields).ErrorfOp(op, "request failed")
|
||||
return err
|
||||
}
|
||||
if isDebug {
|
||||
ctx.logger.WithFields(fields).DebugfOp(op, "request handled")
|
||||
return nil
|
||||
}
|
||||
ctx.logger.WithFields(fields).InfofOp(op, "request handled")
|
||||
return nil
|
||||
}
|
||||
|
||||
func path(r *http.Request) string {
|
||||
path := r.URL.Path
|
||||
if path == "" {
|
||||
path = "/"
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func lantency(startTime time.Time, stopTime time.Time) string {
|
||||
return strconv.FormatInt(int64(stopTime.Sub(startTime)), 10)
|
||||
}
|
||||
|
||||
func latencyHuman(startTime time.Time, stopTime time.Time) string {
|
||||
return stopTime.Sub(startTime).String()
|
||||
}
|
||||
|
||||
func bytesIn(r *http.Request) string {
|
||||
bytesIn := r.Header.Get(echo.HeaderContentLength)
|
||||
if bytesIn == "" {
|
||||
bytesIn = "0"
|
||||
}
|
||||
return bytesIn
|
||||
}
|
||||
|
||||
func bytesOut(r *echo.Response) string {
|
||||
return strconv.FormatInt(r.Size, 10)
|
||||
}
|
||||
7
internal/app/xhttp/pkg/context/doc.go
Normal file
7
internal/app/xhttp/pkg/context/doc.go
Normal file
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
Package context extends the default echo.Context.
|
||||
|
||||
All functions return our standard xerror.Error
|
||||
in case of error.
|
||||
*/
|
||||
package context
|
||||
253
internal/app/xhttp/pkg/resource/arg.go
Normal file
253
internal/app/xhttp/pkg/resource/arg.go
Normal file
@@ -0,0 +1,253 @@
|
||||
package resource
|
||||
|
||||
import (
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/conf"
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/xassert"
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/xerror"
|
||||
)
|
||||
|
||||
// ArgKey is a type for
|
||||
// arguments' keys.
|
||||
type ArgKey string
|
||||
|
||||
const (
|
||||
// ResultFilenameArgKey is the key
|
||||
// of the argument "resultFilename".
|
||||
ResultFilenameArgKey ArgKey = "resultFilename"
|
||||
// WaitTimeoutArgKey is the key
|
||||
// of the argument "waitTimeout".
|
||||
WaitTimeoutArgKey ArgKey = "waitTimeout"
|
||||
// WebhookURLArgKey is the key
|
||||
// of the argument "webhookURL".
|
||||
WebhookURLArgKey ArgKey = "webhookURL"
|
||||
// WebhookURLTimeoutArgKey is the key
|
||||
// of the argument "webhookURLTimeout".
|
||||
WebhookURLTimeoutArgKey ArgKey = "webhookURLTimeout"
|
||||
// RemoteURLArgKey is the key
|
||||
// of the argument "remoteURL".
|
||||
RemoteURLArgKey ArgKey = "remoteURL"
|
||||
// WaitDelayArgKey is the key
|
||||
// of the argument "waitDelay".
|
||||
WaitDelayArgKey ArgKey = "waitDelay"
|
||||
// PaperWidthArgKey is the key
|
||||
// of the argument "paperWidth".
|
||||
PaperWidthArgKey ArgKey = "paperWidth"
|
||||
// PaperHeightArgKey is the key
|
||||
// of the argument "paperHeight".
|
||||
PaperHeightArgKey ArgKey = "paperHeight"
|
||||
// MarginTopArgKey is the key
|
||||
// of the argument "marginTop".
|
||||
MarginTopArgKey ArgKey = "marginTop"
|
||||
// MarginBottomArgKey is the key
|
||||
// of the argument "marginBottom".
|
||||
MarginBottomArgKey ArgKey = "marginBottom"
|
||||
// MarginLeftArgKey is the key
|
||||
// of the argument "marginLeft".
|
||||
MarginLeftArgKey ArgKey = "marginLeft"
|
||||
// MarginRightArgKey is the key
|
||||
// of the argument "marginRight".
|
||||
MarginRightArgKey ArgKey = "marginRight"
|
||||
// LandscapeArgKey is the key
|
||||
// of the argument "landscape".
|
||||
LandscapeArgKey ArgKey = "landscape"
|
||||
)
|
||||
|
||||
/*
|
||||
ArgKeys returns a slice
|
||||
containing all available
|
||||
arguments' keys.
|
||||
*/
|
||||
func ArgKeys() []ArgKey {
|
||||
return []ArgKey{
|
||||
ResultFilenameArgKey,
|
||||
WaitTimeoutArgKey,
|
||||
WebhookURLArgKey,
|
||||
WebhookURLTimeoutArgKey,
|
||||
RemoteURLArgKey,
|
||||
WaitDelayArgKey,
|
||||
PaperWidthArgKey,
|
||||
PaperHeightArgKey,
|
||||
MarginTopArgKey,
|
||||
MarginBottomArgKey,
|
||||
MarginLeftArgKey,
|
||||
MarginRightArgKey,
|
||||
LandscapeArgKey,
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
WaitTimeoutArg is a helper for retrieving
|
||||
the "waitTimeout" argument as float64.
|
||||
|
||||
It also validates it against the application
|
||||
configuration.
|
||||
*/
|
||||
func WaitTimeoutArg(r Resource, config conf.Config) (float64, error) {
|
||||
const op string = "resource.WaitTimeoutArg"
|
||||
result, err := r.Float64Arg(
|
||||
WaitTimeoutArgKey,
|
||||
config.DefaultWaitTimeout(),
|
||||
xassert.Float64NotInferiorTo(0),
|
||||
xassert.Float64NotSuperiorTo(config.MaximumWaitTimeout()),
|
||||
)
|
||||
if err != nil {
|
||||
return result, xerror.New(op, err)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
/*
|
||||
WaitDelayArg is a helper for retrieving
|
||||
the "waitDelay" argument as float64.
|
||||
|
||||
It also validates it against the application
|
||||
configuration.
|
||||
*/
|
||||
func WaitDelayArg(r Resource, config conf.Config) (float64, error) {
|
||||
const (
|
||||
op string = "resource.WaitDelayArg"
|
||||
defaultWaitDelay float64 = 0.0
|
||||
)
|
||||
result, err := r.Float64Arg(
|
||||
WaitDelayArgKey,
|
||||
defaultWaitDelay,
|
||||
xassert.Float64NotInferiorTo(0.0),
|
||||
xassert.Float64NotSuperiorTo(config.MaximumWaitDelay()),
|
||||
)
|
||||
if err != nil {
|
||||
return result, xerror.New(op, err)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
/*
|
||||
PaperSizeArgs is a helper for retrieving
|
||||
the "paperWidth" and "paperHeight" arguments
|
||||
as float64.
|
||||
*/
|
||||
func PaperSizeArgs(r Resource) (float64, float64, error) {
|
||||
const (
|
||||
op string = "resource.PaperSizeArgs"
|
||||
defaultPaperWidth float64 = 8.27
|
||||
defaultPaperHeight float64 = 11.7
|
||||
)
|
||||
resolver := func() (float64, float64, error) {
|
||||
paperWidth, err := r.Float64Arg(
|
||||
PaperWidthArgKey,
|
||||
defaultPaperWidth,
|
||||
xassert.Float64NotInferiorTo(0.0),
|
||||
)
|
||||
if err != nil {
|
||||
return defaultPaperWidth,
|
||||
defaultPaperHeight,
|
||||
err
|
||||
}
|
||||
paperHeight, err := r.Float64Arg(
|
||||
PaperHeightArgKey,
|
||||
defaultPaperHeight,
|
||||
xassert.Float64NotInferiorTo(0.0),
|
||||
)
|
||||
if err != nil {
|
||||
return defaultPaperWidth,
|
||||
defaultPaperHeight,
|
||||
err
|
||||
}
|
||||
return paperWidth,
|
||||
paperHeight,
|
||||
nil
|
||||
}
|
||||
paperWidth, paperHeight,
|
||||
err := resolver()
|
||||
if err != nil {
|
||||
return paperWidth,
|
||||
paperHeight,
|
||||
xerror.New(op, err)
|
||||
}
|
||||
return paperWidth,
|
||||
paperHeight,
|
||||
nil
|
||||
}
|
||||
|
||||
/*
|
||||
MarginArgs is a helper for retrieving
|
||||
the "marginTop", "marginBottom", "marginLeft"
|
||||
and "marginRight" arguments as float64.
|
||||
*/
|
||||
func MarginArgs(r Resource) (float64, float64, float64, float64, error) {
|
||||
const (
|
||||
op string = "resource.MarginArgs"
|
||||
defaultMarginTop float64 = 1.0
|
||||
defaultMarginBottom float64 = 1.0
|
||||
defaultMarginLeft float64 = 1.0
|
||||
defaultMarginRight float64 = 1.0
|
||||
)
|
||||
resolver := func() (float64, float64, float64, float64, error) {
|
||||
marginTop, err := r.Float64Arg(
|
||||
MarginTopArgKey,
|
||||
defaultMarginTop,
|
||||
xassert.Float64NotInferiorTo(0.0),
|
||||
)
|
||||
if err != nil {
|
||||
return defaultMarginTop,
|
||||
defaultMarginBottom,
|
||||
defaultMarginLeft,
|
||||
defaultMarginRight,
|
||||
err
|
||||
}
|
||||
marginBottom, err := r.Float64Arg(
|
||||
MarginBottomArgKey,
|
||||
defaultMarginBottom,
|
||||
xassert.Float64NotInferiorTo(0.0),
|
||||
)
|
||||
if err != nil {
|
||||
return defaultMarginTop,
|
||||
defaultMarginBottom,
|
||||
defaultMarginLeft,
|
||||
defaultMarginRight,
|
||||
err
|
||||
}
|
||||
marginLeft, err := r.Float64Arg(
|
||||
MarginLeftArgKey,
|
||||
defaultMarginLeft,
|
||||
xassert.Float64NotInferiorTo(0.0),
|
||||
)
|
||||
if err != nil {
|
||||
return defaultMarginTop,
|
||||
defaultMarginBottom,
|
||||
defaultMarginLeft,
|
||||
defaultMarginRight,
|
||||
err
|
||||
}
|
||||
marginRight, err := r.Float64Arg(
|
||||
MarginRightArgKey,
|
||||
defaultMarginRight,
|
||||
xassert.Float64NotInferiorTo(0.0),
|
||||
)
|
||||
if err != nil {
|
||||
return defaultMarginTop,
|
||||
defaultMarginBottom,
|
||||
defaultMarginLeft,
|
||||
defaultMarginRight,
|
||||
err
|
||||
}
|
||||
return marginTop,
|
||||
marginBottom,
|
||||
marginLeft,
|
||||
marginRight,
|
||||
nil
|
||||
}
|
||||
marginTop, marginBottom, marginLeft, marginRight,
|
||||
err := resolver()
|
||||
if err != nil {
|
||||
return marginTop,
|
||||
marginBottom,
|
||||
marginLeft,
|
||||
marginRight,
|
||||
xerror.New(op, err)
|
||||
}
|
||||
return marginTop,
|
||||
marginBottom,
|
||||
marginLeft,
|
||||
marginRight,
|
||||
nil
|
||||
}
|
||||
8
internal/app/xhttp/pkg/resource/doc.go
Normal file
8
internal/app/xhttp/pkg/resource/doc.go
Normal file
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
Package resource helps managing
|
||||
arguments and files for a conversion.
|
||||
|
||||
All functions return our standard xerror.Error
|
||||
in case of error.
|
||||
*/
|
||||
package resource
|
||||
91
internal/app/xhttp/pkg/resource/file.go
Normal file
91
internal/app/xhttp/pkg/resource/file.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package resource
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/xerror"
|
||||
)
|
||||
|
||||
// file represents a file within the resource.
|
||||
type file struct {
|
||||
fpath string
|
||||
}
|
||||
|
||||
// write writes given content to the
|
||||
// resourceFile location.
|
||||
func (f file) write(in io.Reader) error {
|
||||
const op string = "resource.file.write"
|
||||
resolver := func() error {
|
||||
out, err := os.Create(f.fpath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close() // nolint: errcheck
|
||||
if err := out.Chmod(0644); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := io.Copy(out, in); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := out.Seek(0, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if err := resolver(); err != nil {
|
||||
return xerror.New(op, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// content returns the string content of
|
||||
// the file.
|
||||
func (f file) content() (string, error) {
|
||||
const op string = "resource.file.content"
|
||||
b, err := ioutil.ReadFile(f.fpath)
|
||||
if err != nil {
|
||||
return "", xerror.New(op, err)
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
/*
|
||||
HeaderFooterContents is a helper for retrieving
|
||||
the content of the files "header.html"
|
||||
and "footer.html".
|
||||
*/
|
||||
func HeaderFooterContents(r Resource) (string, string, error) {
|
||||
const (
|
||||
op string = "resource.HeaderFooterContents"
|
||||
defaultHeaderFooterHTML string = "<html><head></head><body></body></html>"
|
||||
)
|
||||
resolver := func() (string, string, error) {
|
||||
headerHTML, err := r.Fcontent("header.html", defaultHeaderFooterHTML)
|
||||
if err != nil {
|
||||
return defaultHeaderFooterHTML,
|
||||
defaultHeaderFooterHTML,
|
||||
err
|
||||
}
|
||||
footerHTML, err := r.Fcontent("footer.html", defaultHeaderFooterHTML)
|
||||
if err != nil {
|
||||
return defaultHeaderFooterHTML,
|
||||
defaultHeaderFooterHTML,
|
||||
err
|
||||
}
|
||||
return headerHTML,
|
||||
footerHTML,
|
||||
nil
|
||||
}
|
||||
headerHTML, footerHTML,
|
||||
err := resolver()
|
||||
if err != nil {
|
||||
return headerHTML,
|
||||
footerHTML,
|
||||
xerror.New(op, err)
|
||||
}
|
||||
return headerHTML,
|
||||
footerHTML,
|
||||
nil
|
||||
}
|
||||
227
internal/app/xhttp/pkg/resource/resource.go
Normal file
227
internal/app/xhttp/pkg/resource/resource.go
Normal file
@@ -0,0 +1,227 @@
|
||||
package resource
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/xassert"
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/xerror"
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/xlog"
|
||||
)
|
||||
|
||||
/*
|
||||
TemporaryDirectory is the directory
|
||||
where all the resources directory
|
||||
are located.
|
||||
*/
|
||||
const TemporaryDirectory string = "tmp"
|
||||
|
||||
// Resource helps managing
|
||||
// arguments and files for a conversion.
|
||||
type Resource struct {
|
||||
logger xlog.Logger
|
||||
dirPath string
|
||||
args map[ArgKey]string
|
||||
files map[string]file
|
||||
}
|
||||
|
||||
// New creates a Resource where its files will
|
||||
// be located in the given directory name.
|
||||
func New(logger xlog.Logger, directoryName string) (Resource, error) {
|
||||
const op string = "resource.New"
|
||||
resolver := func() (string, error) {
|
||||
dirPath := fmt.Sprintf("%s/%s", TemporaryDirectory, directoryName)
|
||||
if err := os.MkdirAll(dirPath, 0755); err != nil {
|
||||
return "", err
|
||||
}
|
||||
absDirPath, err := filepath.Abs(dirPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return absDirPath, nil
|
||||
}
|
||||
dirPath, err := resolver()
|
||||
if err != nil {
|
||||
return Resource{}, xerror.New(op, err)
|
||||
}
|
||||
logger.DebugfOp(op, "resource directory '%s' created", directoryName)
|
||||
return Resource{
|
||||
logger: logger,
|
||||
dirPath: dirPath,
|
||||
args: make(map[ArgKey]string),
|
||||
files: make(map[string]file),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Close removes the working directory of the
|
||||
// Resource if it exists.
|
||||
func (r Resource) Close() error {
|
||||
const op string = "resource.Resource.Close"
|
||||
if _, err := os.Stat(r.dirPath); os.IsNotExist(err) {
|
||||
r.logger.DebugfOp(op, "resource directory '%s' does not exist, nothing to remove", r.dirPath)
|
||||
return nil
|
||||
}
|
||||
if err := os.RemoveAll(r.dirPath); err != nil {
|
||||
return xerror.New(op, err)
|
||||
}
|
||||
r.logger.DebugfOp(op, "resource directory '%s' removed", r.dirPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithArg add a new argument to the Resource.
|
||||
func (r *Resource) WithArg(key ArgKey, value string) {
|
||||
const op string = "resource.Resource.WithArg"
|
||||
r.args[key] = value
|
||||
r.logger.DebugfOp(op, "added '%s' with value '%s' to resource args", key, value)
|
||||
}
|
||||
|
||||
// WithFile add a new file to the Resource.
|
||||
func (r *Resource) WithFile(filename string, in io.Reader) error {
|
||||
const op string = "resource.Resource.WithFile"
|
||||
fpath := fmt.Sprintf("%s/%s", r.dirPath, filename)
|
||||
file := file{fpath: fpath}
|
||||
if err := file.write(in); err != nil {
|
||||
return xerror.New(op, err)
|
||||
}
|
||||
r.files[filename] = file
|
||||
r.logger.DebugfOp(op, "resource file '%s' created", filename)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DirPath returns the directory path
|
||||
// of the Resource.
|
||||
func (r Resource) DirPath() string {
|
||||
return r.dirPath
|
||||
}
|
||||
|
||||
// HasArg returns true if given key exists
|
||||
// among the Resource and its value is not empty.
|
||||
func (r Resource) HasArg(key ArgKey) bool {
|
||||
if v, ok := r.args[key]; ok {
|
||||
return v != ""
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/*
|
||||
StringArg returns the value of the
|
||||
argument identified by given key.
|
||||
|
||||
It works in the same manner as xassert.String.
|
||||
*/
|
||||
func (r Resource) StringArg(key ArgKey, defaultValue string, rules ...xassert.RuleString) (string, error) {
|
||||
const op string = "resource.Resource.StringArg"
|
||||
result, err := xassert.String(string(key), r.args[key], defaultValue, rules...)
|
||||
if err != nil {
|
||||
return result, xerror.New(op, err)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
/*
|
||||
Int64Arg returns the int64 representation of the
|
||||
argument identified by given key.
|
||||
|
||||
It works in the same manner as xassert.Int64.
|
||||
*/
|
||||
func (r Resource) Int64Arg(key ArgKey, defaultValue int64, rules ...xassert.RuleInt64) (int64, error) {
|
||||
const op string = "resource.Resource.Int64Arg"
|
||||
result, err := xassert.Int64(string(key), r.args[key], defaultValue, rules...)
|
||||
if err != nil {
|
||||
return result, xerror.New(op, err)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
/*
|
||||
Float64Arg returns the float64 representation of the
|
||||
argument identified by given key.
|
||||
|
||||
It works in the same manner as xassert.Float64.
|
||||
*/
|
||||
func (r Resource) Float64Arg(key ArgKey, defaultValue float64, rules ...xassert.RuleFloat64) (float64, error) {
|
||||
const op string = "resource.Resource.Float64Arg"
|
||||
result, err := xassert.Float64(string(key), r.args[key], defaultValue, rules...)
|
||||
if err != nil {
|
||||
return result, xerror.New(op, err)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
/*
|
||||
BoolArg returns the boolean representation of the
|
||||
argument identified by given key.
|
||||
|
||||
It works in the same manner as xassert.Bool.
|
||||
*/
|
||||
func (r Resource) BoolArg(key ArgKey, defaultValue bool) (bool, error) {
|
||||
const op string = "resource.Resource.BoolArg"
|
||||
result, err := xassert.Bool(string(key), r.args[key], defaultValue)
|
||||
if err != nil {
|
||||
return result, xerror.New(op, err)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Fpath returns the path of the given filename.
|
||||
// This filename should exist whithin the Resource.
|
||||
func (r Resource) Fpath(filename string) (string, error) {
|
||||
const op string = "resource.Resource.Fpath"
|
||||
file, ok := r.files[filename]
|
||||
if !ok {
|
||||
return "", xerror.Invalid(
|
||||
op,
|
||||
fmt.Sprintf("resource file '%s' does not exist", filename),
|
||||
nil,
|
||||
)
|
||||
}
|
||||
return file.fpath, nil
|
||||
}
|
||||
|
||||
/*
|
||||
Fpaths returns the paths of the files
|
||||
having one of the given file extensions.
|
||||
|
||||
It should found at least one path.
|
||||
*/
|
||||
func (r Resource) Fpaths(exts ...string) ([]string, error) {
|
||||
const op string = "resource.Resource.Fpaths"
|
||||
var fpaths []string
|
||||
for filename, file := range r.files {
|
||||
for _, ext := range exts {
|
||||
if filepath.Ext(filename) == ext {
|
||||
fpaths = append(fpaths, file.fpath)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(fpaths) == 0 {
|
||||
return nil, xerror.Invalid(
|
||||
op,
|
||||
fmt.Sprintf("no resource file found for extensions '%v'", exts),
|
||||
nil,
|
||||
)
|
||||
}
|
||||
return fpaths, nil
|
||||
}
|
||||
|
||||
/*
|
||||
Fcontent returns the string content of the
|
||||
given filename.
|
||||
|
||||
If filename does not exist within the Resource,
|
||||
returns the default value.
|
||||
*/
|
||||
func (r Resource) Fcontent(filename, defaultValue string) (string, error) {
|
||||
const op string = "resource.Resource.Fcontent"
|
||||
file, ok := r.files[filename]
|
||||
if !ok {
|
||||
return defaultValue, nil
|
||||
}
|
||||
content, err := file.content()
|
||||
if err != nil {
|
||||
return "", xerror.New(op, err)
|
||||
}
|
||||
return content, nil
|
||||
}
|
||||
33
internal/app/xhttp/xhttp.go
Normal file
33
internal/app/xhttp/xhttp.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package xhttp
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/conf"
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/pm2"
|
||||
)
|
||||
|
||||
// New returns a custom echo.Echo.
|
||||
func New(config conf.Config, processes ...pm2.Process) *echo.Echo {
|
||||
srv := echo.New()
|
||||
srv.HideBanner = true
|
||||
srv.HidePort = true
|
||||
srv.Use(contextMiddleware(config, processes...))
|
||||
srv.Use(loggerMiddleware())
|
||||
srv.Use(cleanupMiddleware())
|
||||
srv.Use(errorMiddleware())
|
||||
srv.GET(pingEndpoint, pingHandler)
|
||||
srv.POST(mergeEndpoint, 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)
|
||||
}
|
||||
if !config.DisableUnoconv() {
|
||||
g.POST(officeEndpoint, officeHandler)
|
||||
}
|
||||
return srv
|
||||
}
|
||||
Reference in New Issue
Block a user