/
client.go
138 lines (119 loc) · 3.47 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
// Package client defines types and functions for interacting with
// docconv HTTP servers.
package client
import (
"bytes"
"encoding/json"
"fmt"
"io"
"mime/multipart"
"net/http"
"os"
)
// DefaultProtocol is the default protocol used to construct paths
// when making docconv requests.
const DefaultProtocol = "http://"
// DefaultEndpoint is the default endpoint address (host:port) for
// docconv HTTP servers.
const DefaultEndpoint = "localhost:8888"
// DefaultHTTPClient is the default HTTP client used to make requests
// to docconv HTTP servers.
var DefaultHTTPClient = http.DefaultClient
// Opt is an option used in New to create Clients.
type Opt func(*Client)
// WithEndpoint set the endpoint on a Client.
func WithEndpoint(endpoint string) Opt {
return func(c *Client) {
c.endpoint = endpoint
}
}
// WithHTTPClient sets the *http.Client used for all underlying
// calls.
func WithHTTPClient(client *http.Client) Opt {
return func(c *Client) {
c.httpClient = client
}
}
// WithProtocol sets the protocol used in HTTP requests. Currently this
// must be either http:// or https://.
func WithProtocol(protocol string) Opt {
return func(c *Client) {
c.protocol = protocol
}
}
// New creates a new docconv client for interacting with a docconv HTTP
// server.
func New(opts ...Opt) *Client {
c := &Client{
endpoint: DefaultEndpoint,
protocol: DefaultProtocol,
httpClient: DefaultHTTPClient,
}
for _, opt := range opts {
opt(c)
}
return c
}
// Client is a docconv HTTP client. Use New to make new Clients.
type Client struct {
endpoint string
protocol string
httpClient *http.Client
}
// Response is from docconv.Response copied here to avoid dependency on
// the docconv package.
type Response struct {
Body string `json:"body"`
Meta map[string]string `json:"meta"`
MSecs uint32 `json:"msecs"`
Error string `json:"error"`
}
// Convert a file from a local path using the http client
func (c *Client) Convert(r io.Reader, filename string) (*Response, error) {
buf := &bytes.Buffer{}
w := multipart.NewWriter(buf)
part, err := w.CreateFormFile("input", filename)
if err != nil {
return nil, err
}
if n, err := io.Copy(part, r); err != nil {
return nil, fmt.Errorf("could not copy file data into request (failed after %d bytes): %w", n, err)
}
if err := w.Close(); err != nil {
return nil, err
}
req, err := http.NewRequest("POST", fmt.Sprintf("%v%v/convert", c.protocol, c.endpoint), buf)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", w.FormDataContentType())
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
res := &Response{}
if resp.StatusCode != http.StatusOK {
err := json.NewDecoder(resp.Body).Decode(&res)
if err != nil {
// Invalid JSON can come from proxies etc, so try
// to give something meaningful.
return nil, fmt.Errorf("non-OK status from convert server: %d (%v)", resp.StatusCode, http.StatusText(resp.StatusCode))
}
return nil, fmt.Errorf("non-OK status from convert server: %d (%v) with error: %v", resp.StatusCode, http.StatusText(resp.StatusCode), res.Error)
}
if err := json.NewDecoder(resp.Body).Decode(&res); err != nil {
return nil, err
}
return res, nil
}
// ConvertPath uses the docconv Client to convert the local file
// found at path.
func ConvertPath(c *Client, path string) (*Response, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
return c.Convert(f, f.Name())
}