Commit be920fb4 authored by Ruslan Safin's avatar Ruslan Safin

initial

parents
.idea
\ No newline at end of file
MIT License
Copyright (c) 2019 TheCodingMachine
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
\ No newline at end of file
# Gotenberg Go client
A simple Go client for interacting with a Gotenberg API.
## Install
```bash
$ go get -u github.com/thecodingmachine/gotenberg-go-client/v7
```
## Usage
```golang
import (
"time"
"net/http"
"github.com/thecodingmachine/gotenberg-go-client/v7"
)
// create the client.
client := &gotenberg.Client{Hostname: "http://localhost:3000"}
// ... or use your own *http.Client.
httpClient := &http.Client{
Timeout: time.Duration(5) * time.Second,
}
client := &gotenberg.Client{Hostname: "http://localhost:3000", HTTPClient: httpClient}
// prepare the files required for your conversion.
// from a path.
index, _ := gotenberg.NewDocumentFromPath("index.html", "/path/to/file")
// ... or from a string.
index, _ := gotenberg.NewDocumentFromString("index.html", "<html>Foo</html>")
// ... or from bytes.
index, _ := gotenberg.NewDocumentFromBytes("index.html", []byte("<html>Foo</html>"))
header, _ := gotenberg.NewDocumentFromPath("header.html", "/path/to/file")
footer, _ := gotenberg.NewDocumentFromPath("footer.html", "/path/to/file")
style, _ := gotenberg.NewDocumentFromPath("style.css", "/path/to/file")
img, _ := gotenberg.NewDocumentFromPath("img.png", "/path/to/file")
req := gotenberg.NewHTMLRequest(index)
req.Header(header)
req.Footer(footer)
req.Assets(style, img)
req.PaperSize(gotenberg.A4)
req.Margins(gotenberg.NoMargins)
req.Scale(0.75)
// store method allows you to... store the resulting PDF in a particular destination.
client.Store(req, "path/you/want/the/pdf/to/be/stored.pdf")
// if you wish to redirect the response directly to the browser, you may also use:
resp, _ := client.Post(req)
```
For more complete usages, head to the [documentation](https://thecodingmachine.github.io/gotenberg).
## Badges
[![Travis CI](https://travis-ci.org/thecodingmachine/gotenberg-go-client.svg?branch=master)](https://travis-ci.org/thecodingmachine/gotenberg-go-client)
[![GoDoc](https://godoc.org/github.com/thecodingmachine/gotenberg-go-client?status.svg)](https://godoc.org/github.com/thecodingmachine/gotenberg-go-client)
[![Go Report Card](https://goreportcard.com/badge/github.com/thecodingmachine/gotenberg-go-client)](https://goreportcard.com/report/thecodingmachine/gotenberg-go-client)
\ No newline at end of file
package gotenberg
import (
"fmt"
"strconv"
)
const (
waitDelay string = "waitDelay"
paperWidth string = "paperWidth"
paperHeight string = "paperHeight"
marginTop string = "marginTop"
marginBottom string = "marginBottom"
marginLeft string = "marginLeft"
marginRight string = "marginRight"
landscapeChrome string = "landscape"
pageRanges string = "pageRanges"
googleChromeRpccBufferSize string = "googleChromeRpccBufferSize"
scale string = "scale"
)
// nolint: gochecknoglobals
var (
// A3 paper size.
A3 = [2]float64{11.7, 16.5}
// A4 paper size.
A4 = [2]float64{8.27, 11.7}
// A5 paper size.
A5 = [2]float64{5.8, 8.3}
// A6 paper size.
A6 = [2]float64{4.1, 5.8}
// Letter paper size.
Letter = [2]float64{8.5, 11}
// Legal paper size.
Legal = [2]float64{8.5, 14}
// Tabloid paper size.
Tabloid = [2]float64{11, 17}
)
// nolint: gochecknoglobals
var (
// NoMargins removes margins.
NoMargins = [4]float64{0, 0, 0, 0}
// NormalMargins uses 1 inche margins.
NormalMargins = [4]float64{1, 1, 1, 1}
// LargeMargins uses 2 inche margins.
LargeMargins = [4]float64{2, 2, 2, 2}
)
type chromeRequest struct {
header Document
footer Document
*request
}
func newChromeRequest() *chromeRequest {
return &chromeRequest{nil, nil, newRequest()}
}
// WaitDelay sets waitDelay form field.
func (req *chromeRequest) WaitDelay(delay float64) {
req.values[waitDelay] = strconv.FormatFloat(delay, 'f', 2, 64)
}
// Header sets header form file.
func (req *chromeRequest) Header(header Document) {
req.header = header
}
// Footer sets footer form file.
func (req *chromeRequest) Footer(footer Document) {
req.footer = footer
}
// PaperSize sets paperWidth and paperHeight form fields.
func (req *chromeRequest) PaperSize(size [2]float64) {
req.values[paperWidth] = fmt.Sprintf("%f", size[0])
req.values[paperHeight] = fmt.Sprintf("%f", size[1])
}
// Margins sets marginTop, marginBottom,
// marginLeft and marginRight form fields.
func (req *chromeRequest) Margins(margins [4]float64) {
req.values[marginTop] = fmt.Sprintf("%f", margins[0])
req.values[marginBottom] = fmt.Sprintf("%f", margins[1])
req.values[marginLeft] = fmt.Sprintf("%f", margins[2])
req.values[marginRight] = fmt.Sprintf("%f", margins[3])
}
// Landscape sets landscape form field.
func (req *chromeRequest) Landscape(isLandscape bool) {
req.values[landscapeChrome] = strconv.FormatBool(isLandscape)
}
// PageRanges sets pageRanges form field.
func (req *chromeRequest) PageRanges(ranges string) {
req.values[pageRanges] = ranges
}
// GoogleChromeRpccBufferSize sets googleChromeRpccBufferSize form field.
func (req *chromeRequest) GoogleChromeRpccBufferSize(bufferSize int64) {
req.values[googleChromeRpccBufferSize] = strconv.FormatInt(bufferSize, 10)
}
// Scale sets scale form field
func (req *chromeRequest) Scale(scaleFactor float64) {
req.values[scale] = fmt.Sprintf("%f", scaleFactor)
}
package gotenberg
import (
"bytes"
"errors"
"fmt"
"io"
"mime/multipart"
"net/http"
"os"
"path/filepath"
"runtime"
"strconv"
)
const (
resultFilename string = "resultFilename"
waitTimeout string = "waitTimeout"
webhookURL string = "webhookURL"
webhookURLTimeout string = "webhookURLTimeout"
webhookURLBaseHTTPHeaderKey string = "Gotenberg-Webhookurl-"
)
// Client facilitates interacting with
// the Gotenberg API.
type Client struct {
Hostname string
HTTPClient *http.Client
}
// Request is a type for sending
// form values and form files to
// the Gotenberg API.
type Request interface {
postURL() string
customHTTPHeaders() map[string]string
formValues() map[string]string
formFiles() map[string]Document
}
type request struct {
httpHeaders map[string]string
values map[string]string
}
func newRequest() *request {
return &request{
httpHeaders: make(map[string]string),
values: make(map[string]string),
}
}
// ResultFilename sets resultFilename form field.
func (req *request) ResultFilename(filename string) {
req.values[resultFilename] = filename
}
// WaitTimeout sets waitTimeout form field.
func (req *request) WaitTimeout(timeout float64) {
req.values[waitTimeout] = strconv.FormatFloat(timeout, 'f', 2, 64)
}
// WebhookURL sets webhookURL form field.
func (req *request) WebhookURL(url string) {
req.values[webhookURL] = url
}
// WebhookURLTimeout sets webhookURLTimeout form field.
func (req *request) WebhookURLTimeout(timeout float64) {
req.values[webhookURLTimeout] = strconv.FormatFloat(timeout, 'f', 2, 64)
}
// AddWebhookURLHTTPHeader add a webhook custom HTTP header.
func (req *request) AddWebhookURLHTTPHeader(key, value string) {
key = fmt.Sprintf("%s%s", webhookURLBaseHTTPHeaderKey, key)
req.httpHeaders[key] = value
}
func (req *request) customHTTPHeaders() map[string]string {
return req.httpHeaders
}
func (req *request) formValues() map[string]string {
return req.values
}
// Post sends a request to the Gotenberg API
// and returns the response.
func (c *Client) Post(req Request) (*http.Response, error) {
body, contentType, err := multipartForm(req)
if err != nil {
return nil, err
}
if c.HTTPClient == nil {
c.HTTPClient = &http.Client{}
}
URL := fmt.Sprintf("%s%s", c.Hostname, req.postURL())
httpReq, err := http.NewRequest(http.MethodPost, URL, body)
if err != nil {
return nil, err
}
httpReq.Header.Set("Content-Type", contentType)
for key, value := range req.customHTTPHeaders() {
httpReq.Header.Set(key, value)
}
resp, err := c.HTTPClient.Do(httpReq) /* #nosec */
if err != nil {
return nil, err
}
return resp, nil
}
// Store creates the resulting PDF to given destination.
func (c *Client) Store(req Request, dest string) error {
if hasWebhook(req) {
return errors.New("cannot use Store method with a webhook")
}
resp, err := c.Post(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return errors.New("failed to generate the result PDF")
}
return writeNewFile(dest, resp.Body)
}
func hasWebhook(req Request) bool {
webhookURL, ok := req.formValues()[webhookURL]
if !ok {
return false
}
return webhookURL != ""
}
func writeNewFile(fpath string, in io.Reader) error {
if err := os.MkdirAll(filepath.Dir(fpath), 0755); err != nil {
return fmt.Errorf("%s: making directory for file: %v", fpath, err)
}
out, err := os.Create(fpath)
if err != nil {
return fmt.Errorf("%s: creating new file: %v", fpath, err)
}
defer out.Close() // nolint: errcheck
err = out.Chmod(0644)
if err != nil && runtime.GOOS != "windows" {
return fmt.Errorf("%s: changing file mode: %v", fpath, err)
}
_, err = io.Copy(out, in)
if err != nil {
return fmt.Errorf("%s: writing file: %v", fpath, err)
}
return nil
}
func fileExists(name string) bool {
_, err := os.Stat(name)
return !os.IsNotExist(err)
}
func multipartForm(req Request) (*bytes.Buffer, string, error) {
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
defer writer.Close() // nolint: errcheck
for filename, document := range req.formFiles() {
in, err := document.Reader()
if err != nil {
return nil, "", fmt.Errorf("%s: creating reader: %v", filename, err)
}
defer in.Close() // nolint: errcheck
part, err := writer.CreateFormFile("files", filename)
if err != nil {
return nil, "", fmt.Errorf("%s: creating form file: %v", filename, err)
}
_, err = io.Copy(part, in)
if err != nil {
return nil, "", fmt.Errorf("%s: copying data: %v", filename, err)
}
}
for name, value := range req.formValues() {
if err := writer.WriteField(name, value); err != nil {
return nil, "", fmt.Errorf("%s: writing form field: %v", name, err)
}
}
return body, writer.FormDataContentType(), nil
}
/*
Package gotenberg is a Go client for
interacting with a Gotenberg API.
For more complete usages, head to the documentation:
https://thecodingmachine.github.io/gotenberg/
*/
package gotenberg
package gotenberg
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"strings"
)
// Document reprents a file which
// will be send to the Gotenberg API.
type Document interface {
Filename() string
Reader() (io.ReadCloser, error)
}
type document struct {
filename string
}
func (doc *document) Filename() string {
return doc.filename
}
type documentFromPath struct {
fpath string
*document
}
// NewDocumentFromPath creates a Document from
// a file path.
func NewDocumentFromPath(filename, fpath string) (Document, error) {
if !fileExists(fpath) {
return nil, fmt.Errorf("%s: file %s does not exist", fpath, filename)
}
return &documentFromPath{
fpath,
&document{filename},
}, nil
}
func (doc *documentFromPath) Reader() (io.ReadCloser, error) {
in, err := os.Open(doc.fpath)
if err != nil {
return nil, fmt.Errorf("%s: opening file: %v", doc.Filename(), err)
}
return in, nil
}
type documentFromString struct {
data string
*document
}
// NewDocumentFromString creates a Document from
// a string.
func NewDocumentFromString(filename, data string) (Document, error) {
if len(data) == 0 {
return nil, fmt.Errorf("%s: string is empty", filename)
}
return &documentFromString{
data,
&document{filename},
}, nil
}
func (doc *documentFromString) Reader() (io.ReadCloser, error) {
return ioutil.NopCloser(strings.NewReader(doc.data)), nil
}
type documentFromBytes struct {
data []byte
*document
}
// NewDocumentFromBytes creates a Document from
// bytes.
func NewDocumentFromBytes(filename string, data []byte) (Document, error) {
if len(data) == 0 {
return nil, fmt.Errorf("%s: bytes are empty", filename)
}
return &documentFromBytes{
data,
&document{filename},
}, nil
}
func (doc *documentFromBytes) Reader() (io.ReadCloser, error) {
return ioutil.NopCloser(bytes.NewReader(doc.data)), nil
}
// Compile-time checks to ensure type implements desired interfaces.
var (
_ = Document(new(documentFromPath))
_ = Document(new(documentFromString))
_ = Document(new(documentFromBytes))
)
package gotenberg
// HTMLRequest facilitates HTML conversion
// with the Gotenberg API.
type HTMLRequest struct {
index Document
assets []Document
*chromeRequest
}
// NewHTMLRequest create HTMLRequest.
func NewHTMLRequest(index Document) *HTMLRequest {
return &HTMLRequest{index, []Document{}, newChromeRequest()}
}
// Assets sets assets form files.
func (req *HTMLRequest) Assets(assets ...Document) {
req.assets = assets
}
func (req *HTMLRequest) postURL() string {
return "/convert/html"
}
func (req *HTMLRequest) formFiles() map[string]Document {
files := make(map[string]Document)
files["index.html"] = req.index
if req.header != nil {
files["header.html"] = req.header
}
if req.footer != nil {
files["footer.html"] = req.footer
}
for _, asset := range req.assets {
files[asset.Filename()] = asset
}
return files
}
// Compile-time checks to ensure type implements desired interfaces.
var (
_ = Request(new(HTMLRequest))
)
package gotenberg
// MarkdownRequest facilitates Markdown conversion
// with the Gotenberg API.
type MarkdownRequest struct {
index Document
markdowns []Document
assets []Document
*chromeRequest
}
// NewMarkdownRequest create MarkdownRequest.
func NewMarkdownRequest(index Document, markdowns ...Document) *MarkdownRequest {
return &MarkdownRequest{index, markdowns, []Document{}, newChromeRequest()}
}
// Assets sets assets form files.
func (req *MarkdownRequest) Assets(assets ...Document) {
req.assets = assets
}
func (req *MarkdownRequest) postURL() string {
return "/convert/markdown"
}
func (req *MarkdownRequest) formFiles() map[string]Document {
files := make(map[string]Document)
files["index.html"] = req.index
for _, markdown := range req.markdowns {
files[markdown.Filename()] = markdown
}
if req.header != nil {
files["header.html"] = req.header
}
if req.footer != nil {
files["footer.html"] = req.footer
}
for _, asset := range req.assets {
files[asset.Filename()] = asset
}
return files
}
// Compile-time checks to ensure type implements desired interfaces.
var (
_ = Request(new(MarkdownRequest))
)
package gotenberg
// MergeRequest facilitates merging PDF
// with the Gotenberg API.
type MergeRequest struct {
pdfs []Document
*request
}
// NewMergeRequest create MergeRequest.
func NewMergeRequest(pdfs ...Document) *MergeRequest {
return &MergeRequest{pdfs, newRequest()}
}
func (req *MergeRequest) postURL() string {
return "/merge"
}
func (req *MergeRequest) formFiles() map[string]Document {
files := make(map[string]Document)
for _, pdf := range req.pdfs {
files[pdf.Filename()] = pdf
}
return files
}
// Compile-time checks to ensure type implements desired interfaces.
var (
_ = Request(new(MergeRequest))
)
package gotenberg
import (
"strconv"
)
const (
landscapeOffice string = "landscape"
pageRangesOffice string = "pageRanges"
)
// OfficeRequest facilitates Office documents
// conversion with the Gotenberg API.
type OfficeRequest struct {
docs []Document
*request
}
// NewOfficeRequest create OfficeRequest.
func NewOfficeRequest(docs ...Document) *OfficeRequest {
return &OfficeRequest{docs, newRequest()}
}
// Landscape sets landscape form field.
func (req *OfficeRequest) Landscape(isLandscape bool) {
req.values[landscapeOffice] = strconv.FormatBool(isLandscape)
}
// PageRanges sets pageRanges form field.
func (req *OfficeRequest) PageRanges(ranges string) {
req.values[pageRangesOffice] = ranges
}
func (req *OfficeRequest) postURL() string {
return "/convert/office"
}
func (req *OfficeRequest) formFiles() map[string]Document {
files := make(map[string]Document)
for _, doc := range req.docs {
files[doc.Filename()] = doc
}
return files
}
// Compile-time checks to ensure type implements desired interfaces.
var (
_ = Request(new(OfficeRequest))
)
package gotenberg
import "fmt"
const (
remoteURL string = "remoteURL"
remoteURLBaseHTTPHeaderKey string = "Gotenberg-Remoteurl-"
)
// URLRequest facilitates remote URL conversion
// with the Gotenberg API.
type URLRequest struct {
*chromeRequest
}
// NewURLRequest create URLRequest.
func NewURLRequest(url string) *URLRequest {
req := &URLRequest{newChromeRequest()}
req.values[remoteURL] = url
return req