forked from philippschulte/fastly-promises
-
Notifications
You must be signed in to change notification settings - Fork 12
/
Copy pathREADME.md.hbs
306 lines (214 loc) · 14.9 KB
/
README.md.hbs
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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# fastly-native-promises
## DEPRECATION WARNING
**THIS PACKAGE WILL LIKELY BE DEPRECATED IN THE NEAR FUTURE**
As soon as the official [`fastly` package on NPM](https://www.npmjs.com/package/fastly) leaves beta for version 3.0.0, this package
will be deprecated in favor of the official package and this package will no longer receive feature updates and likely be archived after Adobe migrated its own usage of this package.
---
> Native Promise based Fastly API client for Node.js
[](https://www.npmjs.com/package/@adobe/fastly-native-promises)
[](https://codecov.io/gh/adobe/fastly-native-promises)
[](https://circleci.com/gh/adobe/fastly-native-promises)
[](https://github.com/adobe/fastly-native-promises/blob/main/LICENSE)
[](https://github.com/adobe/fastly-native-promises/issues)
## Problem
The callback-based [fastly](https://www.npmjs.com/package/fastly) package is still the most used client on [NPM](https://www.npmjs.com/). However, I needed a client which allows me to perform request sequentially and parallelly without ending up in an untamable [callback hell](http://callbackhell.com/). [Philipp Schulte's fastly-native-promises](https://github.com/philippschulte/fastly-native-promises) client seemed almost perfect, except:
- it uses Axios, which is an additional dependency we'd like to avoid, especially when running inside Adobe I/O Runtime
- it has been missing features and pull requests were merged only slowly
This fork addresses the concerns above but breaks compatibility with Browsers, so that it can only be used in Node JS environments.
## Solution
The `fastly-native-promises` package uses the promise-based HTTP client [Request-Promise-Native](https://github.com/request/request-promise-native) to perform requests to the [Fastly](https://docs.fastly.com/api/) API. [Request-Promise-Native](https://github.com/request/request-promise-native) supports the native JavaScript [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) API and automatically transforms the data into JSON. Each `fastly-native-promises` API method returns a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) which represents either the completion or failure of the request.
## Table of Contents
- [Security](#security)
- [Install](#install)
- [Usage](#usage)
- [API](#api)
- [Tests](#tests)
- [Contribute](#contribute)
- [License](#license)
## Security
You'll need a [Fastly API Token](https://docs.fastly.com/api/auth#tokens) to use the `fastly-native-promises` library. I recommend using a token with [global scope](https://docs.fastly.com/api/auth#access) to be able to use all `fastly-native-promises` API methods.
## Install
This is a [Node.js](https://nodejs.org/) module available through the [npm registry](https://www.npmjs.com/). Installation is done using the [`npm install` command](https://docs.npmjs.com/getting-started/installing-npm-packages-locally):
```bash
$ npm install @adobe/fastly-native-promises
```
## Changes
See the [changelog](CHANGELOG.md).
## Usage
```javascript
import fastly from '@adobe/fastly-native-promises';
// create one or more instances
const service_1 = fastly('token', 'service_id_1');
const serivce_2 = fastly('token', 'service_id_2');
// make changes
service_1.transact(async () => {
return this.writeS3('test-s3', {
name: 'test-s3',
bucket_name: 'my_corporate_bucket',
access_key: 'AKIAIOSFODNN7EXAMPLE',
secret_key: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
});
});
service_2.transact(async () => {
return this.writeBigquery('test-bq', {
name: 'test-bq',
format: '{\n "timestamp":"%{begin:%Y-%m-%dT%H:%M:%S}t",\n "time_elapsed":%{time.elapsed.usec}V,\n "is_tls":%{if(req.is_ssl, "true", "false")}V,\n "client_ip":"%{req.http.Fastly-Client-IP}V",\n "geo_city":"%{client.geo.city}V",\n "geo_country_code":"%{client.geo.country_code}V",\n "request":"%{req.request}V",\n "host":"%{req.http.Fastly-Orig-Host}V",\n "url":"%{json.escape(req.url)}V",\n "request_referer":"%{json.escape(req.http.Referer)}V",\n "request_user_agent":"%{json.escape(req.http.User-Agent)}V",\n "request_accept_language":"%{json.escape(req.http.Accept-Language)}V",\n "request_accept_charset":"%{json.escape(req.http.Accept-Charset)}V",\n "cache_status":"%{regsub(fastly_info.state, "^(HIT-(SYNTH)|(HITPASS|HIT|MISS|PASS|ERROR|PIPE)).*", "\\\\2\\\\3") }V"\n}',
user: '[email protected]',
project_id: 'example-fastly-log',
dataset: 'fastly_log_test',
table: 'fastly_logs',
template_suffix: null,
secret_key: '-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC7bPG9yaIYd5AL\nmvOaYvNozFJB/VWS53KWBll769kJvlmgMks6r6Xrv8w6rjxWKjZeDrnXVf7UDa0F\nckPPIFvXRxahftWFMGArw0lIvQzgT4/BlndXU5RNxfah/8m7q/GIF6oNYWzfJwvv\nzodxDUqIRH2e2JWidNRjElHuogYHLhV4O/od5pAkfDwak/ihuuh/2VA3Auwb3nph\ndX2F0JBs14oPKZUTYUUSzUQY5IMxSxYUA4Q7W4v21x1EnJt+biXOrERk1rm4ieEE\nU3WkjR5c5gvG8xcWyYod87RNFELmIhCCytI1+t5C3Em/jPsQFtLzwHpbNhdW4oEm\nn7d06n75AgMBAAECggEAWRh26lNZfOwJS5sDRlbXgu/uAnSdI1JmxC6Mhz4cVGdq\nT57Y6DLrWuA4A4UkJYm3gorZiSXWF5PQthAVb/bf8bxXY7nZYpEWhnc09SD5aAAq\nREp0vMx8aWQ709K2YUJg+zDUo7u2d3YmVH8HH5TD43c7iDFJIIsNE3N4A0p+NxZ+\nw06FFW+fz/etrWiNyhrlTsbkMbSgU+GpFFBq1pCd0ni5d1YM1rsaAaUpmkwdjgjL\noDs+M/L/HtqfEhyZNdw8JF7EJXVE1bIl7/NL0rBInhyO28FcB56t/AG5nzXKFI/c\nc+IO7d6MOOqiGRLRWZItEpnyzuV8DZo461wy1hSvqQKBgQDhSsg2cHkTrtBW8x0A\n3BwB/ygdkkxm1OIvfioT+JBneRufUPvVIM2aPZBBGKEedDAmIGn/8f9XAHhKjs8B\nEsPRgE206s4+hnrTcK7AeWWPvM9FDkrkQCoJFuJrNy9mJt8gs7AnnoBa9u/J4naW\ne1tfC8fUfsa7kdzblDhcRQ8FhwKBgQDU+N4kPzIdUuJDadd6TkBbjUNPEfZzU5+t\nIike2VSRhApxAxviUnTDsTROwJRzKik9w7gIMka8Ek+nmLNMEtds77ttcGQRdu16\n+vT1iualiCJe+/iMbl+PiJtFwhEHECLU9QfgBVS6r2lDAlZA+w6nwCRiidlrObzO\nCXqVOzN3fwKBgAsrOuu//bClHP0ChnCReO38aU+1/gWnDiOOnKVq0DXhAiaOzD1P\nqAG6hZlEkFBDMPWzq62doKv+gPgpRkfmV0DenHuYnGrrHdG3p2IxYoCSuq/QupPA\nPpU+xjDMhpQI30zuu4/rQq+/yDl4+aoSKYB3xAtb0Zxg6dMU8QpZ/hmnAoGBAIFu\nIesbcQR7O8FGkMrmxZweNNrYCtQ57R/WU/FImWm6OnJGNmsMO6Q2jJiT12RKKjg8\nOxrYGz7vTfOIDOddyAiPhXPUSyyF/3uvCrIzUUsmeeUJ8xq9dVwQ5HS3pYuKVfDg\nXYHbG4w9UJaF1A+3xEdUsYglSLouo7z/67zH9tZXAoGBAKpsdjSd3R+llaAv2HQ8\nGMlN92UTr5i9w++QMXq4qspH5NEYqz3NHbKuYthZqxEsRUZbRP50eDWU4jvxFVJl\nLBFINp6B+3AsIme0YCyOaleB/Cy0347miSinSv2I6QiH6dQxHdHzrG+x1evS/76f\nKT0KS+ySjCAEWgg4v+mjUDUV\n-----END PRIVATE KEY-----\n',
response_condition: '',
});
// optional, but speeds up end of process
await service_1.discard();
await service_2.discard();
});
```
### Promises
Purge all domains of the active version:
1. Get all the versions.
2. Filter out the active version.
3. Get all the domains for the active version.
4. Purge all the domains.
5. Log the status text for each purge request.
```javascript
import fastly from '@adobe/fastly-native-promises';
const service = fastly('token', 'service_id');
function handler() {
service.readVersions()
.then(versions => {
const active = versions.data.filter(version => version.active)[0];
return service.readDomains(active.number);
})
.then(domains => {
return Promise.all(domains.data.map(domain => service.purgeIndividual(domain.name)));
})
.then(purges => {
purges.forEach(purge => console.log(purge.statusText));
})
.catch(e => {
console.log('Shoot!');
});
}
```
### Async/Await
Update `first_byte_timeout` property for every backend and service if the value is less than 5000 milliseconds:
1. Get all the services associated with the Fastly API token.
2. Filter out the service IDs.
3. Iterate over all services synchronously.
4. Get all the versions.
5. Filter out the active version.
6. Get all the backends for the active version.
7. Filter out the affected backends.
8. Continue with the next service if there are no affected backends.
9. Clone the active version.
10. Update all the affected backends parallelly.
11. Activate the cloned version.
```javascript
import fastly from '@adobe/fastly-native-promises';
const account = fastly('token');
async function handler() {
try {
const services = await account.readServices();
const ids = services.data.map(service => service.id);
for (const id of ids) {
const service = fastly('token', id);
const versions = await service.readVersions();
const active = versions.data.filter(version => version.active)[0];
const backends = await service.readBackends(active.number);
const affected = backends.data.filter(backend => backend.first_byte_timeout < 5000);
if (!affected.length) continue;
const clone = await service.cloneVersion(active.number);
await Promise.all(affected.map(backend => service.updateBackend(clone.data.number, backend.name, { first_byte_timeout: 5000 })));
await service.activateVersion(clone.data.number);
}
} catch (e) {
console.log('Shoot!');
}
}
```
### Response Schema
Each `fastly-native-promises` API method returns the following response object:
```javascript
{
// the HTTP status code from the server response
status: 200,
// the HTTP status message from the server response
statusText: 'OK',
// the headers that the server responded with
headers: {},
// the options that were provided to request for the request
config: {},
// the request that generated the response
request: {},
// the response that was provided by the server
data: {}
}
```
## Retrieving Request Statistics
The `Fastly` instance has a `requestmonitor` property that can be used to retrieve request statistics:
- `requestmonitor.count` for the total number of requests.
- `requestmonitor.remaining` for the number of requests remaining according to Fastly's API Rate limit for the hour or `undefined` (if no modifying requests have been made yet).
- `requestmonitor.edgedurations` for an array of API processing durations (in milliseconds, measured from the edge).
- `requestmonitor.durations` for an array of request durations (in milliseconds, measured from the client, i.e. including network latency).
With `requestmonitor.stats` you can get all of that in one object, including minimum, maximum and mean durations for all requests.
## Guarding against Rate Limits
Using the `requestmonitor.remaining` property, you can make sure that you still have sufficient requests before you hit the rate limit.
When using the `instance.transact` method, you can furthermore provide a minimum for the necessary available request limit so that after the initial cloning of the version no additional requests will be made if the API rate limit will be exceeded. This allows you to fail fast in case of rate limit issues.
## High-Level Helpers
While most functionality is a low-level wrapper of the Fastly, API, we provide a couple of higher-level helper functions in properties of the `Fastly` instance.
### Conditions Helper in `fastly.conditions`
The conditions helper eases the creation and management of conditions.
```javascript
const fastly = require('fastly-native-promises');
const instance = fastly('mykey', 'service-config');
const update = fastly.conditions.update(1, 'REQUEST', 'Created as an Example', 'example');
const conditions = await update('req.url.basename == "new.html"', 'req.url.basename == "index.html"');
console.log('Created a condition matching index.html with following name', conditions['req.url.basename == "index.html"'].name);
```
`fastly.conditions.update` can be called with the parameters `version` (service config version), `type` (condition type, either `REQUEST`, `RESPONSE`, or `CACHE`), `comment` (a comment that will be visible in the Fastly UI), `nameprefix` (a common prefix for the condition name) to get a new function `update` that performs the update.
When `update` is called with a list of `statements` in VCL condition language, it will synchronize the list of conditions passed in with the conditions that already exist in the Fastly service config. All conditions that share the same `nameprefix`, but are no longer used get deleted, new conditions that don't exist yet will get created (unchanged conditions aren't touched, reducing the number of requests made upon updates).
The return value of `update` is an object that maps condition statement to the condition object. This allows re-using the condition in other Fastly API calls.
### Header Helper in `fastly.headers`
The headers helper eases the creation and management of conditional headers.
```javascript
import fastly from '@adobe/fastly-native-promises';
const instance = fastly('mykey', 'service-config');
const update = fastly.headers.update(
1,
'REQUEST', // apply a request condition
'Created as an Example', // use following comment for conditions
'example', // name-prefix for all generated conditions and headers
'set', // set the header
'http.Location' // which header (Location)
'request' // in the request handling
);
await update(
{
condition: 'req.url.basename == "new.html"',
expression: '"https://new.example.com"',
},
{
condition: 'req.url.basename == "index.html"',
expression: 'https://www.example.com',
});
```
`fastly.headers.update` can be called with the parameters `version` (service config version), `type` (condition type, either `REQUEST`, `RESPONSE`, or `CACHE`), `comment` (a comment that will be visible in the Fastly UI), `nameprefix` (a common prefix for the condition name), `action` (what to do with the header, can be `set`, `append`, or `delete`), `header` (the name of the header – remember to include `http.` in the value), `sub` (the subroutine where the header is applied, can be `request`, `fetch`, `cache`, or `response`) to get a new function `update` that performs the update.
When `update` is called with a list of `objects` that looks like `{ condition: 'req.url ~ "foo/(.*)/bar"', expression: '"bar/" + re.group.1 + "/foo"'}`, i.e. pairs of a `condition` (in VCL condition language) and an `expression` (also valid VCL), it will synchronize the list of headers (and resultant conditions) passed in with the headers and conditions that already exist in the Fastly service config. All conditions and headers that share the same `nameprefix`, but are no longer used get deleted, new conditions and headers that don't exist yet will get created (unchanged conditions and headers aren't touched, reducing the number of requests made upon updates).
## API
{{>main}}
## Tests
To run the test suite, first install the dependencies, then run the [`npm test` command](https://docs.npmjs.com/cli/test):
```bash
$ npm install
$ npm test
```
## Contribute
PRs accepted. I am open to suggestions in improving this library. Commit by:
```bash
$ npm run commit
```
## License
Licensed under the [MIT License](LICENSE) © 2017 Philipp Schulte