forked from thecodingmachine/gotenberg-go-client
-
Notifications
You must be signed in to change notification settings - Fork 2
/
client.go
139 lines (112 loc) · 3.29 KB
/
client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
package gotenberg
import (
"context"
"errors"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"runtime"
)
var (
errEmptyHostname = errors.New("empty hostname")
errWebhookNotAllowed = errors.New("webhook is not allowed for request")
errGenerationFailed = errors.New("resulting file could not be generated")
errSendRequestFailed = errors.New("request sending failed")
)
// MultipartRequester is a type for sending form fields and form files (documents) to the Gotenberg API.
type MultipartRequester interface {
endpoint() string
baseRequester
}
// Client facilitates interacting with the Gotenberg API.
type Client struct {
hostname string
httpClient *http.Client
}
// NewClient creates a new gotenberg.Client. If http.Client is passed as nil, then http.DefaultClient is used.
func NewClient(hostname string, httpClient *http.Client) (*Client, error) {
if httpClient == nil {
httpClient = http.DefaultClient
}
if hostname == "" {
return nil, errEmptyHostname
}
return &Client{
hostname: hostname,
httpClient: httpClient,
}, nil
}
// Send sends a request to the Gotenberg API and returns the response.
func (c *Client) Send(ctx context.Context, req MultipartRequester) (*http.Response, error) {
return c.send(ctx, req)
}
func (c *Client) send(ctx context.Context, r MultipartRequester) (*http.Response, error) {
req, err := c.createRequest(ctx, r, r.endpoint())
if err != nil {
return nil, err
}
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, errSendRequestFailed
}
return resp, nil
}
// Store creates the resulting file to given destination.
func (c *Client) Store(ctx context.Context, req MultipartRequester, dest string) error {
return c.store(ctx, req, dest)
}
func (c *Client) store(ctx context.Context, req MultipartRequester, dest string) error {
if hasWebhook(req) {
return errWebhookNotAllowed
}
resp, err := c.send(ctx, req)
if err != nil {
return err
}
defer func() {
_ = resp.Body.Close()
}()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("%w: %d", errGenerationFailed, resp.StatusCode)
}
return writeNewFile(dest, resp.Body)
}
func writeNewFile(fpath string, in io.Reader) error {
if err := os.MkdirAll(filepath.Dir(fpath), 0o755); err != nil {
return fmt.Errorf("making %s directory: %w", fpath, err)
}
out, err := os.Create(fpath)
if err != nil {
return fmt.Errorf("creating %s: %w", fpath, err)
}
defer func() {
if closeErr := out.Close(); closeErr != nil && err == nil {
err = fmt.Errorf("closing %s: %w", fpath, closeErr)
}
}()
if err = out.Chmod(0o644); err != nil && runtime.GOOS != "windows" {
return fmt.Errorf("setting %s permissions: %w", fpath, err)
}
if _, err = io.Copy(out, in); err != nil {
return fmt.Errorf("writing to %s: %w", fpath, err)
}
return nil
}
func (c *Client) createRequest(ctx context.Context, br baseRequester, endpoint string) (*http.Request, error) {
body, contentType, err := multipartForm(br)
if err != nil {
return nil, err
}
url := fmt.Sprintf("%s%s", c.hostname, endpoint)
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, body)
if err != nil {
return nil, fmt.Errorf("creating request: %w", err)
}
req.Header.Set("Content-Type", contentType)
for key, value := range br.customHeaders() {
req.Header.Set(string(key), value)
}
return req, nil
}