-
Notifications
You must be signed in to change notification settings - Fork 0
/
client.go
168 lines (141 loc) · 3.68 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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
// Package recombee implements a client for Recombee API. The client uses batch endpoint to send requests to the API.
//
// For detailed documentation please see Recombee's API reference: https://docs.recombee.com/api.html.
package recombee
import (
"bytes"
"context"
"crypto/hmac"
"crypto/sha1"
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strconv"
"time"
)
type BatchRequest struct {
Requests []Request `json:"requests"`
}
type Response struct {
StatusCode int `json:"statusCode"`
Message string `json:"message"`
}
// BatchResponse represents a message returned by Recombee Batch API.
type BatchResponse []struct {
Code int `json:"code"`
Json json.RawMessage `json:"json"`
}
type Client struct {
// URI where the Recombee API is exposed.
baseURI string
// DatabaseID points on storage where are API calls stores into or retrieves from.
databaseID string
// Authentication token created in Recombee admin.
token []byte
// configurable via options.
requestTimeout time.Duration
maxBatchSize int
distinctRecomms bool //TODO implement
httpClient *http.Client
}
// NewClient returns new Recombee API client.
func NewClient(baseURI string, databaseID string, token string, opts ...ClientOption) (c *Client) {
c = &Client{
baseURI: baseURI,
databaseID: databaseID,
token: []byte(token),
requestTimeout: 5 * time.Minute,
maxBatchSize: 10000, // API limit
}
for _, o := range opts {
o(c)
}
c.httpClient = &http.Client{
Timeout: c.requestTimeout,
}
return
}
func (c *Client) signURL(u *url.URL) (err error) {
q := u.Query()
// add timestamp
q.Set("hmac_timestamp", strconv.FormatInt(time.Now().Unix(), 10))
u.RawQuery = q.Encode()
// create signature
var sum []byte
mac := hmac.New(sha1.New, []byte(c.token))
mac.Write([]byte(u.RequestURI()))
sum = mac.Sum(nil)
// add signature
q.Set("hmac_sign", hex.EncodeToString(sum))
u.RawQuery = q.Encode()
return
}
func (c *Client) batchRequest(ctx context.Context, requests ...Request) (batchResponse BatchResponse, err error) {
var batchRequest = BatchRequest{
Requests: requests,
}
// URL
var u *url.URL
u, err = url.Parse(fmt.Sprintf("%s/%s/batch/", c.baseURI, c.databaseID))
if err != nil {
return
}
err = c.signURL(u)
if err != nil {
return
}
// request
var body = bytes.NewBuffer(nil)
err = json.NewEncoder(body).Encode(batchRequest)
if err != nil {
return
}
var request *http.Request
request, err = http.NewRequestWithContext(ctx, http.MethodPost, u.String(), body)
if err != nil {
return
}
request.Header.Set("Content-Type", "application/json; charset=utf-8")
// response
var response *http.Response
response, err = c.httpClient.Do(request)
if err != nil {
return
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
var resp Response
err = json.NewDecoder(response.Body).Decode(&resp)
if err != nil {
return
}
err = fmt.Errorf("server returned '%s': %s", http.StatusText(response.StatusCode), resp.Message)
return
}
err = json.NewDecoder(response.Body).Decode(&batchResponse)
if err != nil {
return
}
return
}
// Request creates batch request which requests appropriate entity/entities that is/are given by requests.
func (c *Client) Request(ctx context.Context, requests ...Request) (responses BatchResponse, err error) {
if len(requests) == 0 {
return
}
responses = make(BatchResponse, 0, len(requests))
for i := 0; i < len(requests); i += c.maxBatchSize {
var batch []Request
size := min(len(requests)-i, c.maxBatchSize)
batch = requests[i : i+size]
var br BatchResponse
br, err = c.batchRequest(ctx, batch...)
if err != nil {
return
}
responses = append(responses, br...)
}
return
}