1 | # Interceptors
|
2 |
|
3 | - [Incerceptor Principals](#interceptor-principals)
|
4 | - [Provided Interceptors](#interceptor-provided)
|
5 | - [Common Interceptors](#interceptor-provided-common)
|
6 | - [Default Request Interceptor](#module-rest/interceptor/defaultRequest)
|
7 | - [Hypermedia As The Engine Of Application State (HATEOAS) Interceptor](#module-rest/interceptor/hateoas)
|
8 | - [Location Interceptor](#module-rest/interceptor/location)
|
9 | - [MIME Interceptor](#module-rest/interceptor/mime)
|
10 | - [Path Prefix Interceptor](#module-rest/interceptor/pathPrefix)
|
11 | - [Authentication Interceptors](#interceptor-provided-auth)
|
12 | - [Basic Auth Interceptor](#module-rest/interceptor/basicAuth)
|
13 | - [OAuth Interceptor](#module-rest/interceptor/oAuth)
|
14 | - [CSRF Interceptor](#module-rest/interceptor/csrf)
|
15 | - [Error Detection and Recovery Interceptors](#interceptor-provided-error)
|
16 | - [Error Code Interceptor](#module-rest/interceptor/errorCode)
|
17 | - [Retry Interceptor](#module-rest/interceptor/retry)
|
18 | - [Timeout Interceptor](#module-rest/interceptor/timeout)
|
19 | - [Fallback Interceptors](#interceptor-provided-fallback)
|
20 | - [JSONP Interceptor](#module-rest/interceptor/jsonp)
|
21 | - [Cross Domain Request for IE Interceptor](#module-rest/interceptor/ie/xdomain)
|
22 | - [ActiveX XHR for IE Interceptor](#module-rest/interceptor/ie/xhr)
|
23 | - [Custom Interceptors](#interceptor-custom)
|
24 | - [Interceptor Best Practices](#interceptor-custom-practices)
|
25 | - [Example Interceptors by Concept](#interceptor-custom-concepts)
|
26 | - [Augmented Request/Response](#interceptor-custom-concepts-augment)
|
27 | - [Config Initialization](#interceptor-custom-concepts-config)
|
28 | - [Replaced Request/Response](#interceptor-custom-concepts-replace)
|
29 | - [Reentrent Clients](#interceptor-custom-concepts-reentrent)
|
30 | - [Error Creators](#interceptor-custom-concepts-error)
|
31 | - [Error Recovery](#interceptor-custom-concepts-recovery)
|
32 | - [Cancellation](#interceptor-custom-concepts-cancellation)
|
33 | - [Sharred Request/Response Context](#interceptor-custom-concepts-context)
|
34 | - [Async Request/Response](#interceptor-custom-concepts-async)
|
35 | - [Override Parent Client (ComplexRequest)](#interceptor-custom-concepts-parent)
|
36 | - [Abort Request (ComplexRequest)](#interceptor-custom-concepts-abort)
|
37 |
|
38 |
|
39 | <a name="interceptor-principals"></a>
|
40 | ## Incerceptor Principals
|
41 |
|
42 | rest.js distinguishes itself from other HTTP client libraries by providing a minimal core that can be wrapped by more advanced behavior. These configured clients can then be consumed by our application. If a portion of our application needs more advanced behavior, it can continue to wrap the client without impacting other portions of the application. Functional programming FTW.
|
43 |
|
44 | Each [interceptor](interfaces.md#interface-interceptor) is a function that optionally accepts a parent [client](interfaces.md#interface-client) and some configuration returning a new [client](interfaces.md#interface-client).
|
45 |
|
46 | ```javascript
|
47 | // don't do this, there's a better way
|
48 | pathPrefix = require('rest/interceptor/pathPrefix');
|
49 | errorCode = require('rest/interceptor/errorCode');
|
50 | mime = require('rest/interceptor/mime');
|
51 |
|
52 | client = pathPrefix(errorCode(mime(), { code: 500 }), { prefix: 'http://example.com' });
|
53 | ```
|
54 |
|
55 | That works, but it's a mess, don't do it. The configuration is visually separated from the interceptor it belongs to. Glancing at the code, it's hard to know what's going on, never mind the fun of debugging when you give an interceptor the wrong config.
|
56 |
|
57 | ```javascript
|
58 | // better, but can still be improved
|
59 | pathPrefix = require('rest/interceptor/pathPrefix');
|
60 | errorCode = require('rest/interceptor/errorCode');
|
61 | mime = require('rest/interceptor/mime');
|
62 |
|
63 | client = mime();
|
64 | client = errorCode(client, { code: 500 });
|
65 | client = pathPrefix(client, { prefix: 'http://example.com' });
|
66 | ```
|
67 |
|
68 | This example is much more clear. The configuration is now related to it's interceptor. However, it's a bit difficult to follow the `client` var. If we forget to provide the parent client to the next interceptor in the chain, the chain is broken and reset with the default client.
|
69 |
|
70 | ```javascript
|
71 | // here we go
|
72 | rest = require('rest');
|
73 | pathPrefix = require('rest/interceptor/pathPrefix');
|
74 | errorCode = require('rest/interceptor/errorCode');
|
75 | mime = require('rest/interceptor/mime');
|
76 |
|
77 | client = rest.wrap(mime)
|
78 | .wrap(errorCode, { code: 500 })
|
79 | .wrap(pathPrefix, { prefix: 'http://example.com' });
|
80 | ```
|
81 |
|
82 | In this last example, we're no longer redefining the `client` var, there's no confusion about what the `client` does and we can't forget to pass it along. It's clearly the combination of the default client, and the mime, errorCode and pathPrefix interceptors. The configuration for each interceptor is still directly linked with the interceptor.
|
83 |
|
84 | It's important to consider the order that interceptors are applied, as some interceptors are more ideal near the root of the chain, while others are better being last. The request phase of the interceptors is applied from the last chained to the root, while the response phase flows in the opposite direction.
|
85 |
|
86 | Clients may be [declaratively configured using wire.js](wire.md).
|
87 |
|
88 |
|
89 | <a name="interceptor-provided"></a>
|
90 | ## Provided Interceptors
|
91 |
|
92 |
|
93 | <a name="interceptor-provided-common"></a>
|
94 | ### Common Interceptors
|
95 |
|
96 |
|
97 | <a name="module-rest/interceptor/defaultRequest"></a>
|
98 | #### Default Request Interceptor
|
99 |
|
100 | `rest/interceptor/defaultRequest` ([src](../interceptor/defaultRequest.js))
|
101 |
|
102 | Provide default values for the request object. Default values can be provided for the `method`, `path`, `params`, `headers`, `entity`, and/or `mixin`. If the value does not exist in the request already, then the default value is utilized. The `method`, `path` and `entity` values are direct copies, while the `params`, `headers`, and `mixin` values are mixed into the request. In no case will the interceptor overwrite a value in the request.
|
103 |
|
104 | **Phases**
|
105 |
|
106 | - request
|
107 |
|
108 | **Configuration**
|
109 |
|
110 | <table>
|
111 | <tr>
|
112 | <th>Property</th>
|
113 | <th>Required?</th>
|
114 | <th>Default</th>
|
115 | <th>Description</th>
|
116 | </tr>
|
117 | <tr>
|
118 | <td>method</td>
|
119 | <td>optional</td>
|
120 | <td><em>none</em></td>
|
121 | <td>default HTTP method</td>
|
122 | </tr>
|
123 | <tr>
|
124 | <td>path</td>
|
125 | <td>optional</td>
|
126 | <td><em>none</em></td>
|
127 | <td>default path</td>
|
128 | </tr>
|
129 | <tr>
|
130 | <td>params</td>
|
131 | <td>optional</td>
|
132 | <td><em>none</em></td>
|
133 | <td>default params, mixed into request</td>
|
134 | </tr>
|
135 | <tr>
|
136 | <td>headers</td>
|
137 | <td>optional</td>
|
138 | <td><em>none</em></td>
|
139 | <td>default headers, mixed into request</td>
|
140 | </tr>
|
141 | <tr>
|
142 | <td>entity</td>
|
143 | <td>optional</td>
|
144 | <td><em>none</em></td>
|
145 | <td>default entity</td>
|
146 | </tr>
|
147 | <tr>
|
148 | <td>mixin</td>
|
149 | <td>optional</td>
|
150 | <td><em>none</em></td>
|
151 | <td>default extra parameters for the <a href="clients.md#module-rest/client/xhr">XHR object</a> or <a href="clients.md#module-rest/client/node">Node.js</a>.
|
152 | </tr>
|
153 | </table>
|
154 |
|
155 | **Example**
|
156 |
|
157 | ```javascript
|
158 | client = rest.wrap(defaultRequest, { method: 'PUT', entity: 'defaulted' });
|
159 |
|
160 | client({});
|
161 | // resulting request { method: 'PUT', entity: 'defaulted' }
|
162 |
|
163 | client({ entity: 'custom' });
|
164 | // resulting request { method: 'PUT', entity: 'custom' }
|
165 | ```
|
166 |
|
167 | ```javascript
|
168 | client = rest.wrap(defaultRequest, { headers: { 'X-Requested-With': 'rest.js' } });
|
169 |
|
170 | client({});
|
171 | // resulting request { headers: { 'X-Requested-With': 'rest.js' } }
|
172 |
|
173 | client({ headers: { 'Some-Other-Header': 'still here' } });
|
174 | // resulting request { headers: { 'Some-Other-Header': 'still here', 'X-Requested-With': 'rest.js' } }
|
175 |
|
176 | client({ headers: { 'X-Requested-With': 'it a secret' } });
|
177 | // resulting request { headers: { 'X-Requested-With': 'it a secret' } }
|
178 | ```
|
179 |
|
180 |
|
181 | <a name="module-rest/interceptor/hateoas"></a>
|
182 | #### Hypermedia As The Engine Of Application State (HATEOAS) Interceptor
|
183 |
|
184 | `rest/interceptor/hateoas` ([src](../interceptor/hateoas.js))
|
185 |
|
186 | Indexes `links` properties inside an entity to make accessing the related resources easier to access.
|
187 |
|
188 | Links are index in two ways:
|
189 |
|
190 | 1. as link's `rel` which when accessed issues a request for the linked resource. A promise for the related resource is expected to be returned.
|
191 | 2. as link's `rel` with 'Link' appended, as a reference to the link object.
|
192 |
|
193 | The 'Link' response header is also parsed for related resources following rfc5988. The values parsed from the headers are indexed into the response.links object.
|
194 |
|
195 | Also defines a `clientFor` factory function that creates a new client configured to communicate with a related resource.
|
196 |
|
197 | The client for the resource reference and the `clientFor` function can be provided by the `client` config property. This method is also useful if the request for the resource
|
198 |
|
199 | Index links are exposed by default on the entity. A child object may be configed by the 'target' config property.
|
200 |
|
201 | The entire response object graph will be inspected looking for an Array property names `links`; object cycles are detected and not reindexed.
|
202 |
|
203 | **TIP:** The MIME interceptor should be installed before the HATEOAS interceptor to convert the response entity from a string into proper JS Objects.
|
204 |
|
205 | **NOTE:** Native EcmaScript 5 support is required to access related resources implicitly. Polyfills and shims are insufficient. Non-native environment can be supported by using the `clientFor(rel)` method, invoking the return client as normal.
|
206 |
|
207 | **WARNING:** This interceptor is considered experimental, the behavior may change at any time
|
208 |
|
209 | **Phases**
|
210 |
|
211 | - response
|
212 |
|
213 | **Configuration**
|
214 |
|
215 | <table>
|
216 | <tr>
|
217 | <th>Property</th>
|
218 | <th>Required?</th>
|
219 | <th>Default</th>
|
220 | <th>Description</th>
|
221 | </tr>
|
222 | <tr>
|
223 | <td>target</td>
|
224 | <td>optional</td>
|
225 | <td>''</td>
|
226 | <td>property to create on the entity and parse links into. If empty, the response entity is used directly.</td>
|
227 | </tr>
|
228 | <tr>
|
229 | <td>client</td>
|
230 | <td>optional</td>
|
231 | <td><em>this client</em></td>
|
232 | <td>client to use for subsequent requests</td>
|
233 | </tr>
|
234 | </table>
|
235 |
|
236 | **Example**
|
237 |
|
238 | ```javascript
|
239 | // assuming a native ES5 environment
|
240 | client = rest.warp(mime).wrap(hateoas);
|
241 | client({ path: '/people/scott' }).then(function (response) {
|
242 | // assuming response for /people/scott: { entity: '{ "name": "Scott", "links": [ { "rel": "father", "href": "/peopele/ron" } ], ... }', ... }
|
243 | // assuming response for /people/ron: { entity: '{ "name": "Ron", ... }', ... }
|
244 |
|
245 | assert.same('Scott', response.entity.name);
|
246 | return response.entity.father;
|
247 | }).then(function (response) {
|
248 | assert.same('Ron', response.entity.name);
|
249 | });
|
250 | ```
|
251 |
|
252 | ```javascript
|
253 | // fallback for non-native ES5 environments
|
254 | client = rest.wrap(mime).wrap(hateoas);
|
255 | client({ path: '/people/scott' }).then(function (response) {
|
256 | // assuming response for /people/scott: { entity: '{ "name": "Scott", "links": [ { "rel": "father", "href": "/peopele/ron" } ], ... }', ... }
|
257 | // assuming response for /people/ron: { entity: '{ "name": "Ron", ... }', ... }
|
258 |
|
259 | assert.same('Scott', response.entity.name);
|
260 | response.entity.clientFor('father')({}).then(function (father) {
|
261 | assert.same('Ron', father.entity.name);
|
262 | });
|
263 | });
|
264 | ```
|
265 |
|
266 |
|
267 | <a name="module-rest/interceptor/location"></a>
|
268 | #### Location Interceptor
|
269 |
|
270 | `rest/interceptor/location` ([src](../interceptor/location.js))
|
271 |
|
272 | Follows the `Location` header, returning the response of the subsequent request. Browsers will typically automatically follow the location header for redirect in the 300s range, however, they will not follow the Location for a response in the 200s range. Other clients may not follow 300s redirects. This interceptor will always follow a redirect for the original request by default. If configured with `code` the response status code has to be equal or greater than `code` the be treated as a redirect.
|
273 |
|
274 | Subsequent redirects can be automatically followed by including this interceptor twice in the client chain. However, in this situation, redirect loops will not be detected.
|
275 |
|
276 | **Phases**
|
277 |
|
278 | - success
|
279 |
|
280 | **Configuration**
|
281 |
|
282 | <table>
|
283 | <tr>
|
284 | <th>Property</th>
|
285 | <th>Required?</th>
|
286 | <th>Default</th>
|
287 | <th>Description</th>
|
288 | </tr>
|
289 | <tr>
|
290 | <td>client</td>
|
291 | <td>optional</td>
|
292 | <td><em>parent client</em></td>
|
293 | <td>client to use for subsequent requests</td>
|
294 | </tr>
|
295 | <tr>
|
296 | <td>code</td>
|
297 | <td>optional</td>
|
298 | <td>0</td>
|
299 | <td>status code if equal or greater indicates a redirect</td>
|
300 | </tr>
|
301 | </table>
|
302 |
|
303 | **Example**
|
304 |
|
305 | ```javascript
|
306 | client = rest.wrap(location);
|
307 | client({ method: 'POST', path: 'http://example.com/messages', entity: 'hello world' }).then(function (response) {
|
308 | // assuming response for POST: { status: { code: 201 }, headers: { Location: 'http://example.com/messages/1' } }
|
309 | // assuming response for GET: { status: { code: 200 }, entity: 'hello world', ... }
|
310 |
|
311 | assert.same('hello wold', response.entity);
|
312 | assert.same('GET', response.request.method);
|
313 | assert.same('http://example.com/messages/1', response.request.path);
|
314 | });
|
315 | ```
|
316 |
|
317 |
|
318 | <a name="module-rest/interceptor/mime"></a>
|
319 | #### MIME Interceptor
|
320 |
|
321 | `rest/interceptor/mime` ([src](../interceptor/mime.js))
|
322 |
|
323 | Converts request and response entities using the MIME converter registry. Converters are looked up by the `Content-Type` header value. Content types without a converter default to plain text.
|
324 |
|
325 | See the docs for the MIME registry for more information on available converters and how to register custom converters.
|
326 |
|
327 | **Phases**
|
328 |
|
329 | - request
|
330 | - response
|
331 |
|
332 | **Configuration**
|
333 |
|
334 | <table>
|
335 | <tr>
|
336 | <th>Property</th>
|
337 | <th>Required?</th>
|
338 | <th>Default</th>
|
339 | <th>Description</th>
|
340 | </tr>
|
341 | <tr>
|
342 | <td>mime</td>
|
343 | <td>optional</td>
|
344 | <td><code>Content-Type</code> request header, or 'text/plain'</td>
|
345 | <td>MIME type for request entities</td>
|
346 | </tr>
|
347 | <tr>
|
348 | <td>accept</td>
|
349 | <td>optional</td>
|
350 | <td>mime + ', application/json;q=0.8, text/plain;q=0.5, */*;q=0.2'</td>
|
351 | <td><code>Accept</code> header to use for the request</td>
|
352 | </tr>
|
353 | <tr>
|
354 | <td>registry</td>
|
355 | <td>optional</td>
|
356 | <td><em>default registry</em></td>
|
357 | <td>custom MIME registry</td>
|
358 | </tr>
|
359 | </table>
|
360 |
|
361 | **Example**
|
362 |
|
363 | ```javascript
|
364 | client = rest.wrap(mime);
|
365 | client({ path: 'data.json' }).then(function (response) {
|
366 | // for the response: { entity: '{ "key": "value" }', headers: { 'Content-Type': 'application/json', ... } }
|
367 | assert.same('value', response.entity.key);
|
368 | });
|
369 | ```
|
370 |
|
371 | ```javascript
|
372 | client = rest.wrap(mime, { mime: 'application/json' );
|
373 | client({ method: 'POST', entity: { key: 'value' } }).then(function (response) {
|
374 | assert.same('{ "key": "value" }', response.request.entity);
|
375 | assert.same('application/json, application/json;q=0.8, text/plain;q=0.5, */*;q=0.2', response.request.headers['Content-Type']);
|
376 | });
|
377 | ```
|
378 |
|
379 |
|
380 | <a name="module-rest/interceptor/pathPrefix"></a>
|
381 | #### Path Prefix Interceptor
|
382 |
|
383 | `rest/interceptor/pathPrefix` ([src](../interceptor/pathPrefix.js))
|
384 |
|
385 | The path prefix interceptor prepends its value to the path provided in the request. The prefix can be used as a base path that the request path is then made relative to. A slash will be inserted between the prefix and path values if needed.
|
386 |
|
387 | **Phases**
|
388 |
|
389 | - request
|
390 |
|
391 | **Configuration**
|
392 |
|
393 | <table>
|
394 | <tr>
|
395 | <th>Property</th>
|
396 | <th>Required?</th>
|
397 | <th>Default</th>
|
398 | <th>Description</th>
|
399 | </tr>
|
400 | <tr>
|
401 | <td>prefix</td>
|
402 | <td>optional</td>
|
403 | <td><em>empty string</em></td>
|
404 | <td>value to prepend to the request path</td>
|
405 | </tr>
|
406 | </table>
|
407 |
|
408 | **Example**
|
409 |
|
410 | ```javascript
|
411 | client = rest.wrap(pathPrefix, { prefix: 'http://example.com/messages' });
|
412 | client({ path: '1' }).then(function (response) {
|
413 | assert.same('http://example.com/messages/1', response.request.path);
|
414 | });
|
415 | ```
|
416 |
|
417 |
|
418 | <a name="interceptor-provided-auth"></a>
|
419 | ### Authentication Interceptors
|
420 |
|
421 |
|
422 | <a name="module-rest/interceptor/basicAuth"></a>
|
423 | #### Basic Auth Interceptor
|
424 |
|
425 | `rest/interceptor/basicAuth` ([src](../interceptor/basicAuth.js))
|
426 |
|
427 | Apply HTTP Basic Authentication to the request. The username and password can either be provided by the interceptor configuration, or the request.
|
428 |
|
429 | **Phases**
|
430 |
|
431 | - request
|
432 |
|
433 | **Configuration**
|
434 |
|
435 | <table>
|
436 | <tr>
|
437 | <th>Property</th>
|
438 | <th>Required?</th>
|
439 | <th>Default</th>
|
440 | <th>Description</th>
|
441 | </tr>
|
442 | <tr>
|
443 | <td>username</td>
|
444 | <td>optional</td>
|
445 | <td><em>none</em></td>
|
446 | <td>username for the authentication</td>
|
447 | </tr>
|
448 | <tr>
|
449 | <td>password</td>
|
450 | <td>optional</td>
|
451 | <td><em>empty string</em></td>
|
452 | <td>password for the authentication</td>
|
453 | </tr>
|
454 | </table>
|
455 |
|
456 | **Example**
|
457 |
|
458 | ```javascript
|
459 | client = rest.wrap(basicAuth, { username: 'admin', password: 'letmein' });
|
460 | // interceptor config
|
461 | client({}).then(function (response) {
|
462 | assert.same('Basic YWRtaW46bGV0bWVpbg==', response.request.headers.Authorization);
|
463 | });
|
464 | ```
|
465 |
|
466 | ```javascript
|
467 | client = rest.wrap(basicAuth);
|
468 | // request config
|
469 | client({ username: 'admin', password: 'letmein' }).then(function (reponse) {
|
470 | assert.same('Basic YWRtaW46bGV0bWVpbg==', response.request.headers.Authorization);
|
471 | });
|
472 | ```
|
473 |
|
474 |
|
475 | <a name="module-rest/interceptor/oAuth"></a>
|
476 | #### OAuth Interceptor
|
477 |
|
478 | `rest/interceptor/oAuth` ([src](../interceptor/oAuth.js))
|
479 |
|
480 | Support for the OAuth implicit flow. In a separate window users are redirected to the authentication server when a new access token is required. That authentication server may prompt the user to authenticate and/or grant access to the application requesting an access token. The authentication server then redirects the user back to the application which then needs to parse the access token from the URL and pass it back to the intercept via a callback function placed on the window.
|
481 |
|
482 | **TIP:** A client request may take a very long time to respond while the user is being prompted to authenticate. Once the user returns to the app, the original request is made with the new access token. If an access token expires, the next request may take a similarly long time to respond as a new token is obtained from the authorization server. The oAuth interceptor should typically be after time sensitive interceptors such as timeout.
|
483 |
|
484 | **IMPORTANT:** rest.js is only able to provide part of the client flow. When the user is redirected back from the authentication server, the application server must handle the initial request and provide an HTML page with the scripts to parse the URL fragment containing the access token and provide the token to the callback function. As rest.js is not a server side web framework, it is unable to provide support for this part of the oAuth flow.
|
485 |
|
486 | **Phases**
|
487 |
|
488 | - request
|
489 | - response
|
490 |
|
491 | **Configuration**
|
492 |
|
493 | <table>
|
494 | <tr>
|
495 | <th>Property</th>
|
496 | <th>Required?</th>
|
497 | <th>Default</th>
|
498 | <th>Description</th>
|
499 | </tr>
|
500 | <tr>
|
501 | <td>token</td>
|
502 | <td>optional</td>
|
503 | <td><em>none</em></td>
|
504 | <td>pre-configured authorization token obtained by some other means, using this property is uncommon</td>
|
505 | </tr>
|
506 | <tr>
|
507 | <td>clientId</td>
|
508 | <td>required</td>
|
509 | <td><em>none</em></td>
|
510 | <td>the authentication server clientId, this is given to you by the auth server owner</td>
|
511 | </tr>
|
512 | <tr>
|
513 | <td>scope</td>
|
514 | <td>required</td>
|
515 | <td><em>none</em></td>
|
516 | <td>comma separated list of resource server scopes to request an access token for,</td>
|
517 | </tr>
|
518 | <tr>
|
519 | <td>authorizationUrl</td>
|
520 | <td>required</td>
|
521 | <td><em>none</em></td>
|
522 | <td>base URL for the authorization server</td>
|
523 | </tr>
|
524 | <tr>
|
525 | <td>redirectUrl</td>
|
526 | <td>requried</td>
|
527 | <td><em>none</em></td>
|
528 | <td>URL within this page's origin that the authorization server should redirect back to providing the access token</td>
|
529 | </tr>
|
530 | <tr>
|
531 | <td>windowStrategy</td>
|
532 | <td>optional</td>
|
533 | <td>window.open</td>
|
534 | <td>strategy for opening the browser window to the authorization server</td>
|
535 | </tr>
|
536 | <tr>
|
537 | <td>oAuthCallback</td>
|
538 | <td>optional</td>
|
539 | <td><em>none</em></td>
|
540 | <td>callback function to receive the access token, typically used with a custom windowStrategy</td>
|
541 | </tr>
|
542 | <tr>
|
543 | <td>oAuthCallbackName</td>
|
544 | <td>optional</td>
|
545 | <td>'oAuthCallback'</td>
|
546 | <td>name to register the callback function as on the window</td>
|
547 | </tr>
|
548 | </table>
|
549 |
|
550 | **Example**
|
551 |
|
552 | ```javascript
|
553 | client = rest.wrap(oAuth, {
|
554 | clientId: 'assignedByAuthServer',
|
555 | scope: 'read, write, openid',
|
556 | authorizationUrl: 'http://authserver.example.com/oauth',
|
557 | redirectUrl: 'http://myapp.example.com/oauthhandler'
|
558 | });
|
559 | client({ path: 'http://resourceserver.example.com' }).then(function (response) {
|
560 | // authenticated response from the resource server
|
561 | });
|
562 | ```
|
563 |
|
564 |
|
565 | <a name="module-rest/interceptor/csrf"></a>
|
566 | #### CSRF Interceptor
|
567 |
|
568 | `rest/interceptor/csrf` ([src](../interceptor/csrf.js))
|
569 |
|
570 | Applies a Cross-Site Request Forgery protection header to a request
|
571 |
|
572 | CSRF protection helps a server verify that a request came from a trusted client and not another client that was able to masquerade as an authorized client. Sites that use cookie based authentication are particularly vulnerable to request forgeries without extra protection.
|
573 |
|
574 | **Phases**
|
575 |
|
576 | - request
|
577 |
|
578 | **Configuration**
|
579 |
|
580 | <table>
|
581 | <tr>
|
582 | <th>Property</th>
|
583 | <th>Required?</th>
|
584 | <th>Default</th>
|
585 | <th>Description</th>
|
586 | </tr>
|
587 | <tr>
|
588 | <td>name</td>
|
589 | <td>optional</td>
|
590 | <td>'X-Csrf-Token'</td>
|
591 | <td>name of the request header, may be overridden by `request.csrfTokenName`</td>
|
592 | </tr>
|
593 | <tr>
|
594 | <td>token</td>
|
595 | <td>optional</td>
|
596 | <td><em>none</em></td>
|
597 | <td>CSRF token, may be overridden by `request.csrfToken`</td>
|
598 | </tr>
|
599 | </table>
|
600 |
|
601 | **Example**
|
602 |
|
603 | ```javascript
|
604 | client = rest.wrap(csrf, { token: 'abc123xyz789' });
|
605 | // interceptor config
|
606 | client({}).then(function (response) {
|
607 | assert.same('abc123xyz789', response.request.headers['X-Csrf-Token']);
|
608 | });
|
609 | ```
|
610 |
|
611 | ```javascript
|
612 | client = rest.wrap(csrf);
|
613 | // request config
|
614 | client({ csrfToken: 'abc123xyz789' }).then(function (reponse) {
|
615 | assert.same('abc123xyz789', response.request.headers['X-Csrf-Token']);
|
616 | });
|
617 | ```
|
618 |
|
619 |
|
620 | <a name="interceptor-provided-error"></a>
|
621 | ### Error Detection and Recovery Interceptors
|
622 |
|
623 |
|
624 | <a name="module-rest/interceptor/errorCode"></a>
|
625 | #### Error Code Interceptor
|
626 |
|
627 | `rest/interceptor/errorCode` ([src](../interceptor/errorCode.js))
|
628 |
|
629 | Marks a response as an error based on the status code. According to the HTTP spec, 500s status codes are server errors, 400s codes are client errors. rest.js by default will treat any response from a server as successful, this allows interceptors to define what constitutes an error. The errorCode interceptor will mark a request in error if the status code is equal or greater than the configured value.
|
630 |
|
631 | **Phases**
|
632 |
|
633 | - response
|
634 |
|
635 | **Configuration**
|
636 |
|
637 | <table>
|
638 | <tr>
|
639 | <th>Property</th>
|
640 | <th>Required?</th>
|
641 | <th>Default</th>
|
642 | <th>Description</th>
|
643 | </tr>
|
644 | <tr>
|
645 | <td>code</td>
|
646 | <td>optional</td>
|
647 | <td>400</td>
|
648 | <td>status code if equal or greater indicates an error</td>
|
649 | </tr>
|
650 | </table>
|
651 |
|
652 | **Example**
|
653 |
|
654 | ```javascript
|
655 | client = rest.wrap(errorCode);
|
656 | client({}).then(
|
657 | function (response) {
|
658 | // not called
|
659 | },
|
660 | function (response) {
|
661 | assert.same(500, response.status.code);
|
662 | }
|
663 | );
|
664 | ```
|
665 |
|
666 |
|
667 | <a name="module-rest/interceptor/retry"></a>
|
668 | #### Retry Interceptor
|
669 |
|
670 | `rest/interceptor/retry` ([src](../interceptor/retry.js))
|
671 |
|
672 | Reattempts an errored request after a delay. Attempts are scheduled after a failed response is received, the period between requests is the duration of request plus the delay.
|
673 |
|
674 | **Phases**
|
675 |
|
676 | - error
|
677 |
|
678 | **Configuration**
|
679 |
|
680 | <table>
|
681 | <tr>
|
682 | <th>Property</th>
|
683 | <th>Required?</th>
|
684 | <th>Default</th>
|
685 | <th>Description</th>
|
686 | </tr>
|
687 | <tr>
|
688 | <td>initial</td>
|
689 | <td>optional</td>
|
690 | <td>100</td>
|
691 | <td>initial delay in milliseconds after the first error response</td>
|
692 | </tr>
|
693 | <tr>
|
694 | <td>multiplier</td>
|
695 | <td>optional</td>
|
696 | <td>2</td>
|
697 | <td>multiplier for the delay on each subsequent failure used for exponential back offs</td>
|
698 | </tr>
|
699 | <tr>
|
700 | <td>max</td>
|
701 | <td>optional</td>
|
702 | <td>Infinity</td>
|
703 | <td>max delay in milliseconds</td>
|
704 | </tr>
|
705 | </table>
|
706 |
|
707 | **Example**
|
708 |
|
709 | ```javascript
|
710 | client = rest.wrap(retry, { initial: 1e3, max: 10e3 });
|
711 | client({}).then(function (response) {
|
712 | // assuming it takes a minute from the first request to a successful response
|
713 | // requests occur at 0s, 1s, 3s, 7s, 15s, 25s, 35s, 45s, 55s, 65s
|
714 | });
|
715 | ```
|
716 |
|
717 | Commonly combined with the timeout interceptor to define a max period to wait
|
718 |
|
719 | ```javascript
|
720 | client = rest.wrap(retry, { initial: 1e3, max: 10e3 }).wrap(timeout, { timeout 120e3 });
|
721 | client({}).then(
|
722 | function (response) {
|
723 | // called once a request succeeds
|
724 | },
|
725 | function (response) {
|
726 | // called after two minutes waiting, no further retry attempts are made
|
727 | }
|
728 | );
|
729 | ```
|
730 |
|
731 |
|
732 | <a name="module-rest/interceptor/timeout"></a>
|
733 | #### Timeout Interceptor
|
734 |
|
735 | `rest/interceptor/timeout` ([src](../interceptor/timeout.js))
|
736 |
|
737 | Rejects a request that takes longer than the timeout. If a request is in-flight, it is canceled. The timeout value may be specified in the request or the interceptor config.
|
738 |
|
739 | **Phases**
|
740 |
|
741 | - request
|
742 | - response
|
743 |
|
744 | **Configuration**
|
745 |
|
746 | <table>
|
747 | <tr>
|
748 | <th>Property</th>
|
749 | <th>Required?</th>
|
750 | <th>Default</th>
|
751 | <th>Description</th>
|
752 | </tr>
|
753 | <tr>
|
754 | <td>timeout</td>
|
755 | <td>optional</td>
|
756 | <td><em>disabled</em></td>
|
757 | <td>duration in milliseconds before canceling the request. Non-positive values disable the timeout.</td>
|
758 | </tr>
|
759 | </table>
|
760 |
|
761 | **Example**
|
762 |
|
763 | ```javascript
|
764 | client = rest.wrap(timeout, { timeout: 10e3 });
|
765 | client({}).then(
|
766 | function (response) {
|
767 | // called if the response took less then 10 seconds
|
768 | },
|
769 | function (response) {
|
770 | // called if the response took greater then 10 seconds
|
771 | }
|
772 | );
|
773 | ```
|
774 |
|
775 |
|
776 | <a name="interceptor-provided-fallback"></a>
|
777 | ### Fallback Interceptors
|
778 |
|
779 |
|
780 | <a name="module-rest/interceptor/jsonp"></a>
|
781 | #### JSONP Interceptor
|
782 |
|
783 | `rest/interceptor/jsonp` ([src](../interceptor/jsonp.js))
|
784 |
|
785 | Configures a request to use the [JSONP client](clients.md#module-rest/client/jsonp). For most JSONP services, the interceptor defaults are adequate. The script tag and callback function used to load the response, is automatically cleaned up after a response. The callback function may remain after a cancellation in order to avoid script errors in the response if the server responds.
|
786 |
|
787 | **Phases**
|
788 |
|
789 | - request
|
790 |
|
791 | **Configuration**
|
792 |
|
793 | <table>
|
794 | <tr>
|
795 | <th>Property</th>
|
796 | <th>Required?</th>
|
797 | <th>Default</th>
|
798 | <th>Description</th>
|
799 | </tr>
|
800 | <tr>
|
801 | <td>callback.param</td>
|
802 | <td>optional</td>
|
803 | <td>'callback'</td>
|
804 | <td>request param containing the jsonp callback function name</td>
|
805 | </tr>
|
806 | <tr>
|
807 | <td>callback.prefix</td>
|
808 | <td>optional</td>
|
809 | <td>'jsonp'</td>
|
810 | <td>prefix for the jsonp callback function name</td>
|
811 | </tr>
|
812 | <tr>
|
813 | <td>callback.name</td>
|
814 | <td>optional</td>
|
815 | <td><em>generated</em></td>
|
816 | <td>pins the name of the callback function, useful for cases where the server doesn't allow custom callback names. Generally not recommended.</td>
|
817 | </tr>
|
818 | </table>
|
819 |
|
820 | **Example**
|
821 |
|
822 | ```javascript
|
823 | client = rest.wrap(jsonp);
|
824 | client({ path: 'http://ajax.googleapis.com/ajax/services/search/web?v=1.0', params: { q: 'javascript' } }).then(function (response) {
|
825 | // results from google
|
826 | });
|
827 | ```
|
828 |
|
829 |
|
830 | <a name="module-rest/interceptor/ie/xdomain"></a>
|
831 | #### Cross Domain Request for IE Interceptor
|
832 |
|
833 | `rest/interceptor/ie/xdomain` ([src](../interceptor/ie/xdomain.js))
|
834 |
|
835 | Utilizes IE's XDomainRequest support via the [XDomainRequest client](clients.md#module-rest/client/xdr) for making cross origin requests if needed, available and a CORS enabled XMLHttpRequest is not available. XDR request have a number of limitations, see the [XDR client](clients.md#module-rest/client/xdr) for limitation details. Will not interfere if installed in other environments.
|
836 |
|
837 | This interceptor should be installed as close to the root of the interceptor chain as possible. When a XDomainRequest client is needed, the normal parent client will not be invoked.
|
838 |
|
839 | **Phases**
|
840 |
|
841 | - request
|
842 |
|
843 | **Configuration**
|
844 |
|
845 | *none*
|
846 |
|
847 | **Example**
|
848 |
|
849 | ```javascript
|
850 | client = rest.wrap(xdomain)
|
851 | .wrap(defaultRequest, { params: { api_key: '95f41bfa4faa0f43bf7c24795eabbed4', format: 'rest' } });
|
852 | client({ params: { method: 'flickr.test.echo' } }).then(function (response) {
|
853 | // response from flickr
|
854 | });
|
855 | ```
|
856 |
|
857 |
|
858 | <a name="module-rest/interceptor/ie/xhr"></a>
|
859 | #### ActiveX XHR for IE Interceptor
|
860 |
|
861 | `rest/interceptor/ie/xhr` ([src](../interceptor/ie/xhr.js))
|
862 |
|
863 | Attempts to use an ActiveX XHR replacement if a native XMLHttpRequest object is not available. Useful for IE < 9, which does not natively support XMLHttpRequest. Will not interfere if installed in other environments.
|
864 |
|
865 | **Phases**
|
866 | - request
|
867 |
|
868 | **Configuration**
|
869 |
|
870 | *none*
|
871 |
|
872 | **Example**
|
873 |
|
874 | ```javascript
|
875 | client = rest.wrap(xhr);
|
876 | client({}).then(function (response) {
|
877 | // normal XHR response, even in IE without XHR
|
878 | });
|
879 | ```
|
880 |
|
881 |
|
882 | <a name="interceptor-custom"></a>
|
883 | ## Custom Interceptors
|
884 |
|
885 | Creating a custom interceptor is easy. Fundamentally, an interceptor is a function that accepts a client and a configuration object and returns a new client. While not required, it's highly recommended that interceptor authors use the interceptor factory available in `rest/interceptor`.
|
886 |
|
887 | The interceptor factory allows for interception of the request and/or response. Once the interceptor has a handle on the request or response, it can pass through, manipulate or replace the request/response.
|
888 |
|
889 | There are five phases that may be intercepted
|
890 |
|
891 | <table>
|
892 | <tr>
|
893 | <th>Phase</th>
|
894 | <th>Description</th>
|
895 | </tr>
|
896 | <tr>
|
897 | <td>init</td>
|
898 | <td>one time setup of the interceptor config</td>
|
899 | </tr>
|
900 | <tr>
|
901 | <td>request</td>
|
902 | <td>as the request is initiated, before the socket is opened</td>
|
903 | </tr>
|
904 | <tr>
|
905 | <td>response</td>
|
906 | <td>after the response is fully received, success or error</td>
|
907 | </tr>
|
908 | <tr>
|
909 | <td>success</td>
|
910 | <td>a response in a successful state (all responses from servers are successful until an interceptor puts them into an error state)</td>
|
911 | </tr>
|
912 | <tr>
|
913 | <td>error</td>
|
914 | <td>a response in an error state (either a socket/api level error, or a server response handled as an error)</td>
|
915 | </tr>
|
916 | </table>
|
917 |
|
918 | The `response` phase is a catchall for the `success` and `error` phases; if both `response` and `success` handlers are defined, and the request responds normally, then only the `success` phase fires.
|
919 |
|
920 | Request handlers are functions that accept the request object and interceptor config. Response handlers are provided with the same arguments as request handlers, in addition to the client for the handler. The value returned by a handler becomes the request/response for the next handler in the interceptor chain.
|
921 |
|
922 | ```javascript
|
923 | interceptor = require('rest/interceptor');
|
924 | noopInterceptor = interceptor({
|
925 | init: function (config) {
|
926 | // do studd with the config
|
927 | return config;
|
928 | },
|
929 | request: function (request, config, meta) {
|
930 | // do stuff with the request
|
931 | return request;
|
932 | },
|
933 | response: function (response, config, meta) {
|
934 | // do stuff with the response
|
935 | return response;
|
936 | },
|
937 | success: function (response, config, meta) {
|
938 | // do stuff with the response
|
939 | return response;
|
940 | },
|
941 | error: function (response, config, meta) {
|
942 | // do stuff with the response
|
943 | return response;
|
944 | }
|
945 | });
|
946 | ```
|
947 |
|
948 | Promisses representing the request/response may be returned.
|
949 |
|
950 | ```javascript
|
951 | interceptor = require('rest/interceptor');
|
952 | when = require('when');
|
953 | delayRequestInterceptor = interceptor({
|
954 | request: function (request, config) {
|
955 | return when(request).delay(config.delay || 0);
|
956 | }
|
957 | });
|
958 | ```
|
959 |
|
960 | The `meta` argument contains additional information about the context of the request. It contains the `client`, which can be used to make subsequent requests, and the raw `arguments` provided to the client.
|
961 |
|
962 | For interceptors that need to track state between request and response handlers, the context of each handler is shared and unique to each invocation.
|
963 |
|
964 | ```javascript
|
965 | interceptor = require('rest/interceptor');
|
966 | counter = 0;
|
967 | countLoggingInterceptor = interceptor({
|
968 | request: function (request) {
|
969 | this.count = counter++;
|
970 | return request;
|
971 | },
|
972 | response: function (response) {
|
973 | console.log('invocation count: ', this.count);
|
974 | return response;
|
975 | }
|
976 | });
|
977 | ```
|
978 |
|
979 | Success responses can be converted into errors by returning a rejected promise for the response.
|
980 |
|
981 | ```javascript
|
982 | interceptor = require('rest/interceptor');
|
983 | when = require('when');
|
984 | alwaysErrorInterceptor = interceptor({
|
985 | success: function (response) {
|
986 | return when.reject(response);
|
987 | }
|
988 | });
|
989 | ```
|
990 |
|
991 | Error responses can be converted into successes by returning a resolved promise for the response. This is a special ability of the `error` handler and is not applicable to the `response` handler.
|
992 |
|
993 | ```javascript
|
994 | interceptor = require('rest/interceptor');
|
995 | when = require('when');
|
996 | alwaysErrorInterceptor = interceptor({
|
997 | error: function (response) {
|
998 | return when(response);
|
999 | }
|
1000 | });
|
1001 | ```
|
1002 |
|
1003 | Interceptors may also override the default client if a parent client is not provided when instantiating the interceptor.
|
1004 |
|
1005 | ```javascript
|
1006 | interceptor = require('rest/interceptor');
|
1007 | customDefaultClient = require(...);
|
1008 | customDefaultClientInterceptor = interceptor({
|
1009 | client: customDefaultClient
|
1010 | });
|
1011 | ```
|
1012 |
|
1013 | Default configuration values can be provided in the `init` phase. The config object provided is begotten from the config object provided to the interceptor when created. This means that all the properties of the configuration are available, but updates are protected from causing side effects in other interceptors configured with the same config object.
|
1014 |
|
1015 | ```javascript
|
1016 | interceptor = require('rest/interceptor');
|
1017 | defaultedConfigInterceptor = interceptor({
|
1018 | init: function (config) {
|
1019 | config.prop = config.prop || 'default-value';
|
1020 | return config;
|
1021 | }
|
1022 | });
|
1023 | ```
|
1024 |
|
1025 |
|
1026 | <a name="interceptor-custom-practices"></a>
|
1027 | ### Interceptor Best Practices
|
1028 |
|
1029 | - keep interceptors simple, focus on one thing
|
1030 | - avoid replacing the request object, augment it instead
|
1031 | - make properties configurable
|
1032 | - provide sane defaults for configuration properties, avoid required config
|
1033 | - allow a request to override configured values
|
1034 | - provide default configuration values in the 'init' handler
|
1035 |
|
1036 |
|
1037 | <a name="interceptor-custom-concepts"></a>
|
1038 | ### Example Interceptors by Concept
|
1039 |
|
1040 | The interceptors provided with rest.js provide are also good examples. Here are a few interceptors that demonstrate a particular capability. The order of examples within a topic is simple to complex.
|
1041 |
|
1042 | <a name="interceptor-custom-concepts-augment"></a>
|
1043 | **Augmented Request/Response**
|
1044 |
|
1045 | - [rest/interceptor/basicAuth](#module-rest/interceptor/basicAuth)
|
1046 | - [rest/interceptor/pathPrefix](#module-rest/interceptor/pathPrefix)
|
1047 | - [rest/interceptor/defaultRequest](#module-rest/interceptor/defaultRequest)
|
1048 | - [rest/interceptor/mime](#module-rest/interceptor/mime)
|
1049 | - [rest/interceptor/hateoas](#module-rest/interceptor/hateoas)
|
1050 |
|
1051 | <a name="interceptor-custom-concepts-config"></a>
|
1052 | **Config Initialization**
|
1053 |
|
1054 | - [rest/interceptor/errorCode](#module-rest/interceptor/errorCode)
|
1055 | - [rest/interceptor/hateoas](#module-rest/interceptor/hateoas)
|
1056 | - [rest/interceptor/oAuth](#module-rest/interceptor/oAuth)
|
1057 |
|
1058 | <a name="interceptor-custom-concepts-replace"></a>
|
1059 | **Replaced Request/Response**
|
1060 |
|
1061 | - [rest/interceptor/entity](#module-rest/interceptor/entity)
|
1062 |
|
1063 | <a name="interceptor-custom-concepts-reentrent"></a>
|
1064 | **Reentrent Clients**
|
1065 |
|
1066 | - [rest/interceptor/location](#module-rest/interceptor/location)
|
1067 | - [rest/interceptor/retry](#module-rest/interceptor/retry)
|
1068 | - [rest/interceptor/hateoas](#module-rest/interceptor/hateoas)
|
1069 | - [rest/interceptor/oAuth](#module-rest/interceptor/oAuth)
|
1070 |
|
1071 | <a name="interceptor-custom-concepts-error"></a>
|
1072 | **Error Creators**
|
1073 |
|
1074 | - [rest/interceptor/errorCode](#module-rest/interceptor/errorCode)
|
1075 | - [rest/interceptor/timeout](#module-rest/interceptor/timeout)
|
1076 |
|
1077 | <a name="interceptor-custom-concepts-recovery"></a>
|
1078 | **Error Recovery**
|
1079 |
|
1080 | - [rest/interceptor/retry](#module-rest/interceptor/retry)
|
1081 |
|
1082 | <a name="interceptor-custom-concepts-cancellation"></a>
|
1083 | **Cancellation**
|
1084 |
|
1085 | - [rest/interceptor/timeout](#module-rest/interceptor/timeout)
|
1086 |
|
1087 | <a name="interceptor-custom-concepts-context"></a>
|
1088 | **Sharred Request/Response Context**
|
1089 |
|
1090 | - [rest/interceptor/timeout](#module-rest/interceptor/timeout)
|
1091 |
|
1092 | <a name="interceptor-custom-concepts-async"></a>
|
1093 | **Async Request/Response**
|
1094 |
|
1095 | - [rest/interceptor/mime](#module-rest/interceptor/mime)
|
1096 |
|
1097 | <a name="interceptor-custom-concepts-parent"></a>
|
1098 | **Override Parent Client (ComplexRequest)**
|
1099 |
|
1100 | - [rest/interceptor/ie/xdomain](#module-rest/interceptor/ie/xdomain)
|
1101 |
|
1102 | <a name="interceptor-custom-concepts-abort"></a>
|
1103 | **Abort Request (ComplexRequest)**
|
1104 |
|
1105 | - [rest/interceptor/timeout](#module-rest/interceptor/timeout)
|