mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-08 08:32:16 +01:00
373 lines
9.4 KiB
Go
373 lines
9.4 KiB
Go
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/conf"
|
|
"github.com/thecodingmachine/gotenberg/internal/pkg/printer"
|
|
"github.com/thecodingmachine/gotenberg/internal/pkg/xerror"
|
|
"github.com/thecodingmachine/gotenberg/internal/pkg/xrand"
|
|
"github.com/thecodingmachine/gotenberg/internal/pkg/xtime"
|
|
)
|
|
|
|
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(config))
|
|
if !config.DisableGoogleChrome() {
|
|
multipartFormDataEndpoints = append(
|
|
multipartFormDataEndpoints,
|
|
htmlEndpoint(config),
|
|
urlEndpoint(config),
|
|
markdownEndpoint(config),
|
|
)
|
|
}
|
|
if !config.DisableUnoconv() {
|
|
multipartFormDataEndpoints = append(
|
|
multipartFormDataEndpoints,
|
|
officeEndpoint(config),
|
|
)
|
|
}
|
|
for _, endpoint := range multipartFormDataEndpoints {
|
|
if endpoint == path {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// pingHandler is the handler for healthcheck.
|
|
func pingHandler(c echo.Context) error {
|
|
const op string = "xhttp.pingHandler"
|
|
ctx := context.MustCastFromEchoContext(c)
|
|
logger := ctx.XLogger()
|
|
logger.DebugOp(op, "handling ping request...")
|
|
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
|
|
}
|
|
opts.CustomHTTPHeaders = resource.RemoteURLCustomHTTPHeaders(r)
|
|
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.DebugOpf(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.DebugOpf(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.DebugOpf(
|
|
op,
|
|
"no '%s' found, using generated filename '%s'",
|
|
resource.RemoteURLArgKey,
|
|
filename,
|
|
)
|
|
if err := ctx.Attachment(fpath, filename); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
logger.DebugOpf(
|
|
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()
|
|
webhookURL, err := r.StringArg(resource.WebhookURLArgKey, "")
|
|
if err != nil {
|
|
return xerror.New(op, err)
|
|
}
|
|
webhookURLTimeout, err := resource.WebhookURLTimeoutArg(r, ctx.Config())
|
|
if err != nil {
|
|
return xerror.New(op, err)
|
|
}
|
|
go func() {
|
|
defer r.Close()
|
|
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()
|
|
logger.DebugOpf(
|
|
op,
|
|
"preparing to send result file '%s' to '%s'...",
|
|
filename,
|
|
webhookURL,
|
|
)
|
|
httpClient := &http.Client{
|
|
Timeout: xtime.Duration(webhookURLTimeout),
|
|
}
|
|
req, err := http.NewRequest(http.MethodPost, webhookURL, f)
|
|
if err != nil {
|
|
xerr := xerror.New(op, err)
|
|
logger.ErrorOp(xerror.Op(xerr), xerr)
|
|
return
|
|
}
|
|
req.Header.Set(echo.HeaderContentType, "application/pdf")
|
|
// set custom headers (if any).
|
|
customHTTPHeaders := resource.WebhookURLCustomHTTPHeaders(r)
|
|
if len(customHTTPHeaders) > 0 {
|
|
for key, value := range customHTTPHeaders {
|
|
req.Header.Set(key, value)
|
|
logger.DebugOpf(op, "set '%s' to custom HTTP header '%s'", value, key)
|
|
}
|
|
} else {
|
|
logger.DebugOp(op, "skipping custom HTTP headers as none have been provided...")
|
|
}
|
|
// send the result file.
|
|
logger.DebugOpf(
|
|
op,
|
|
"sending result file '%s' to '%s'...",
|
|
filename,
|
|
webhookURL,
|
|
)
|
|
resp, err := httpClient.Do(req) /* #nosec */
|
|
if err != nil {
|
|
xerr := xerror.New(op, err)
|
|
logger.ErrorOp(xerror.Op(xerr), xerr)
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
logger.DebugOpf(
|
|
op,
|
|
"result file '%s' sent to '%s'",
|
|
filename,
|
|
webhookURL,
|
|
)
|
|
}()
|
|
return nil
|
|
}
|