1
|
|
|
/** |
2
|
|
|
* @module http/rest |
3
|
|
|
*/ |
4
|
|
|
|
5
|
|
|
var C = require('./_common') |
6
|
|
|
var M = C.Method |
7
|
|
|
var Client = require('./basic').Client |
8
|
|
|
var Slf4j = require('../logger').Slf4j |
9
|
|
|
|
10
|
|
|
/** |
11
|
|
|
* @interface Serializer |
12
|
|
|
*/ |
13
|
|
|
|
14
|
|
|
/** |
15
|
|
|
* @function Serializer.serialize |
16
|
|
|
* |
17
|
|
|
* @param {object} |
18
|
|
|
* |
19
|
|
|
* @return {string} |
20
|
|
|
*/ |
21
|
|
|
|
22
|
|
|
/** |
23
|
|
|
* @function Serializer.deserialize |
24
|
|
|
* |
25
|
|
|
* @param {string} |
26
|
|
|
* |
27
|
|
|
* @return {object} |
28
|
|
|
*/ |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* @class |
32
|
|
|
* |
33
|
|
|
* @implements Serializer |
34
|
|
|
*/ |
35
|
|
|
function JsonSerializer () { |
36
|
|
|
this.serialize = JSON.stringify |
37
|
|
|
this.deserialize = JSON.parse |
38
|
|
|
} |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* @class RestClientSettings |
42
|
|
|
* |
43
|
|
|
* @property {string|undefined} url String to prepend to every request route. |
44
|
|
|
* @property {Serializer|undefined} serializer Serializer to encode/decode |
45
|
|
|
* messages, default: JsonSerializer |
46
|
|
|
* @property {number|undefined} retries Maximum number of retries for each |
47
|
|
|
* request, default: `4` |
48
|
|
|
* @property {string|undefined} methodOverrideHeader Name of header that should |
49
|
|
|
* conceal real request HTTP method, |
50
|
|
|
* look up `X-HTTP-Method-Override` in nearest search engine. Default: none |
51
|
|
|
* @property {boolean|undefined} retryOnNetworkError Whether request should be |
52
|
|
|
* retried on connection error, |
53
|
|
|
* default: `true` |
54
|
|
|
* @property {boolean|undefined} retryOnServerError Whether request should be |
55
|
|
|
* retried on server error (5xx), |
56
|
|
|
* default: `true` |
57
|
|
|
* @property {HeaderBag|undefined} headers Object containing headers for every |
58
|
|
|
* emitted request, default: `{}` |
59
|
|
|
* @property {int|undefined} timeout Default request timeout in milliseconds, |
60
|
|
|
* may be overriden on per-request level. Falsey values will use no timeout |
61
|
|
|
* at all. |
62
|
|
|
* @property {LoggerOptions} logger Logger options. |
63
|
|
|
* @property {IHttpClient} client Underlying http client |
64
|
|
|
*/ |
65
|
|
|
|
66
|
|
|
// noinspection JSClosureCompilerSyntax |
67
|
|
|
/** |
68
|
|
|
* @class |
69
|
|
|
* |
70
|
|
|
* @implements IRestClient |
71
|
|
|
* |
72
|
|
|
* @param {RestClientSettings|Object} [settings] |
73
|
|
|
* @param {netHttpRequestAsync} [transport] |
74
|
|
|
*/ |
75
|
|
|
function RestClient (settings, transport) { |
76
|
|
|
var self = this |
77
|
|
|
var opts = getDefaults() |
78
|
|
|
var client |
79
|
|
|
var logger |
80
|
|
|
var counter = 0 |
81
|
|
|
|
82
|
|
|
if (settings instanceof Function) { |
83
|
|
|
transport = settings |
84
|
|
|
settings = {} |
85
|
|
|
} |
86
|
|
|
settings = settings || {} |
87
|
|
|
if (typeof settings === 'string' || settings instanceof String) { |
88
|
|
|
settings = {url: settings} |
89
|
|
|
} |
90
|
|
|
Object.keys(opts).forEach(function (key) { |
91
|
|
|
if (key in settings) { |
92
|
|
|
opts[key] = settings[key] |
93
|
|
|
} |
94
|
|
|
}) |
95
|
|
|
client = settings.client || new Client(opts, transport) |
96
|
|
|
logger = Slf4j.factory(opts.logger, 'ama-team.voxengine-sdk.http.rest') |
97
|
|
|
|
98
|
|
|
function execute (request) { |
99
|
|
|
var id = ++counter |
100
|
|
|
request.id = request.id || id |
101
|
|
|
var basicRequest = { |
102
|
|
|
url: request.resource || request.route, // 0.2.0 compatibility |
103
|
|
|
method: request.method, |
104
|
|
|
query: request.query, |
105
|
|
|
headers: request.headers, |
106
|
|
|
timeout: typeof request.timeout === 'number' ? request.timeout : settings.timeout |
107
|
|
|
} |
108
|
|
|
logger.debug('Executing request #{} `{} {}`', request.id, request.method, request.resource) |
109
|
|
|
basicRequest.payload = request.payload ? opts.serializer.serialize(request.payload) : null |
110
|
|
|
return client |
111
|
|
|
.execute(basicRequest) |
112
|
|
|
.then(function (response) { |
113
|
|
|
logger.debug('Request #{} `{} {}` received response with code {}', |
114
|
|
|
request.id, request.method, request.resource, response.code) |
115
|
|
|
return { |
116
|
|
|
code: response.code, |
117
|
|
|
payload: response.payload ? opts.serializer.deserialize(response.payload) : null, |
118
|
|
|
headers: response.headers, |
119
|
|
|
request: request |
120
|
|
|
} |
121
|
|
|
}, function (e) { |
122
|
|
|
logger.debug('Request #{} `{} {}` has finished with error {}', |
123
|
|
|
request.id, request.method, request.resource, e.name) |
124
|
|
|
throw e |
125
|
|
|
}) |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
/** |
129
|
|
|
* @inheritDoc |
130
|
|
|
*/ |
131
|
|
|
function request (method, resource, payload, query, headers, timeout) { |
132
|
|
|
return execute({resource: resource, method: method, query: query, payload: payload, headers: headers, timeout: timeout}) |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
/** |
136
|
|
|
* @inheritDoc |
137
|
|
|
*/ |
138
|
|
|
this.execute = execute |
139
|
|
|
|
140
|
|
|
/** |
141
|
|
|
* @inheritDoc |
142
|
|
|
*/ |
143
|
|
|
this.request = request |
144
|
|
|
|
145
|
|
|
/** |
146
|
|
|
* @inheritDoc |
147
|
|
|
*/ |
148
|
|
|
this.exists = function (resource, query, headers, timeout) { |
149
|
|
|
return request(M.Head, resource, null, query, headers, timeout) |
150
|
|
|
.then(function (response) { |
151
|
|
|
return response.code !== 404 |
152
|
|
|
}) |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
/** |
156
|
|
|
* @inheritDoc |
157
|
|
|
*/ |
158
|
|
|
this.get = function (resource, query, headers, timeout) { |
159
|
|
|
return request(M.Get, resource, null, query, headers, timeout) |
160
|
|
|
.then(function (response) { |
161
|
|
|
return response.code === 404 ? null : response.payload |
162
|
|
|
}) |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
var methods = {create: M.Post, set: M.Put, modify: M.Patch, delete: M.Delete} |
166
|
|
|
Object.keys(methods).forEach(function (method) { |
167
|
|
|
self[method] = function (resource, payload, headers, query, timeout) { |
168
|
|
|
return request(methods[method], resource, payload, query, headers, timeout) |
169
|
|
|
.then(function (response) { |
170
|
|
|
if (response.code === 404) { |
171
|
|
|
var message = 'Modification request has returned 404 status code' |
172
|
|
|
throw new C.NotFoundException(message, response.request, response) |
173
|
|
|
} |
174
|
|
|
return response.payload |
175
|
|
|
}) |
176
|
|
|
} |
177
|
|
|
}) |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
/** |
181
|
|
|
* @return {RestClientSettings} |
182
|
|
|
*/ |
183
|
|
|
function getDefaults () { |
184
|
|
|
return { |
185
|
|
|
url: '', |
186
|
|
|
client: null, |
187
|
|
|
serializer: new JsonSerializer(), |
188
|
|
|
retries: 4, |
189
|
|
|
methodOverrideHeader: null, |
190
|
|
|
retryOnServerError: true, |
191
|
|
|
retryOnNetworkError: true, |
192
|
|
|
headers: {}, |
193
|
|
|
logger: {} |
194
|
|
|
} |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
RestClient.getDefaults = getDefaults |
198
|
|
|
|
199
|
|
|
module.exports = { |
200
|
|
|
Client: RestClient, |
201
|
|
|
/** @deprecated */ |
202
|
|
|
getDefaults: getDefaults |
203
|
|
|
} |
204
|
|
|
|