fastify 2.7.0 → 2.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/Serverless.md +127 -28
- package/fastify.d.ts +1 -1
- package/lib/handleRequest.js +2 -2
- package/package.json +14 -14
- package/test/hooks-async.js +80 -1
package/docs/Serverless.md
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
Run serverless applications and REST APIs using your existing Fastify application.
|
|
4
4
|
|
|
5
|
+
### Contents
|
|
6
|
+
|
|
7
|
+
- [AWS Lambda](#aws-lambda)
|
|
8
|
+
- [Google Cloud Run](#google-cloud-run)
|
|
9
|
+
|
|
5
10
|
### Attention Readers:
|
|
6
11
|
> Fastify is not designed to run on serverless environments.
|
|
7
12
|
The Fastify framework is designed to make implementing a traditional HTTP/S server easy.
|
|
@@ -12,21 +17,20 @@ it is possible to use Fastify in a serverless environment.
|
|
|
12
17
|
Again, keep in mind that this is not Fastify's intended use case and
|
|
13
18
|
we do not test for such integration scenarios.
|
|
14
19
|
|
|
15
|
-
|
|
16
20
|
## AWS Lambda
|
|
17
21
|
|
|
18
22
|
The sample provided allows you to easily build serverless web applications/services
|
|
19
23
|
and RESTful APIs using Fastify on top of AWS Lambda and Amazon API Gateway.
|
|
20
24
|
|
|
21
|
-
*Note:
|
|
25
|
+
*Note: Using [aws-lambda-fastify](https://github.com/fastify/aws-lambda-fastify) is just one possible way.*
|
|
22
26
|
|
|
23
27
|
### app.js
|
|
24
28
|
|
|
25
29
|
```js
|
|
26
30
|
const fastify = require('fastify');
|
|
27
31
|
|
|
28
|
-
function init(
|
|
29
|
-
const app = fastify(
|
|
32
|
+
function init() {
|
|
33
|
+
const app = fastify();
|
|
30
34
|
app.get('/', (request, reply) => reply.send({ hello: 'world' }));
|
|
31
35
|
return app;
|
|
32
36
|
}
|
|
@@ -43,11 +47,9 @@ if (require.main !== module) {
|
|
|
43
47
|
}
|
|
44
48
|
```
|
|
45
49
|
|
|
46
|
-
You can simply wrap your initialization code by offering to inject an optional [serverFactory](https://www.fastify.io/docs/latest/Server/#serverfactory).
|
|
47
|
-
|
|
48
50
|
When executed in your lambda function we don't need to listen to a specific port,
|
|
49
51
|
so we just export the wrapper function `init` in this case.
|
|
50
|
-
The [`lambda.js`](https://www.fastify.io/docs/latest/
|
|
52
|
+
The [`lambda.js`](https://www.fastify.io/docs/latest/Serverless/#lambda-js) file will use this export.
|
|
51
53
|
|
|
52
54
|
When you execute your Fastify application like always,
|
|
53
55
|
i.e. `node app.js` *(the detection for this could be `require.main === module`)*,
|
|
@@ -56,31 +58,28 @@ you can normally listen to your port, so you can still run your Fastify function
|
|
|
56
58
|
### lambda.js
|
|
57
59
|
|
|
58
60
|
```js
|
|
59
|
-
const
|
|
61
|
+
const awsLambdaFastify = require('aws-lambda-fastify')
|
|
60
62
|
const init = require('./app');
|
|
61
63
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
awsServerlessExpress.proxy(server, event, context, 'CALLBACK', callback);
|
|
74
|
-
});
|
|
75
|
-
};
|
|
64
|
+
const proxy = awsLambdaFastify(init())
|
|
65
|
+
// or
|
|
66
|
+
// const proxy = awsLambdaFastify(init(), { binaryMimeTypes: ['application/octet-stream'] })
|
|
67
|
+
|
|
68
|
+
exports.handler = proxy;
|
|
69
|
+
// or
|
|
70
|
+
// exports.handler = (event, context, callback) => proxy(event, context, callback);
|
|
71
|
+
// or
|
|
72
|
+
// exports.handler = (event, context) => proxy(event, context);
|
|
73
|
+
// or
|
|
74
|
+
// exports.handler = async (event, context) => proxy(event, context);
|
|
76
75
|
```
|
|
77
76
|
|
|
78
|
-
We
|
|
79
|
-
(make sure you install the dependency `npm i --save aws-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
77
|
+
We just require [aws-lambda-fastify](https://github.com/fastify/aws-lambda-fastify)
|
|
78
|
+
(make sure you install the dependency `npm i --save aws-lambda-fastify`) and our
|
|
79
|
+
[`app.js`](https://www.fastify.io/docs/latest/Serverless/#app-js) file and call the
|
|
80
|
+
exported `awsLambdaFastify` function with the `app` as the only parameter.
|
|
81
|
+
The resulting `proxy` function has the correct signature to be used as lambda `handler` function.
|
|
82
|
+
This way all the incoming events (API Gateway requests) are passed to the `proxy` function of [aws-lambda-fastify](https://github.com/fastify/aws-lambda-fastify).
|
|
84
83
|
|
|
85
84
|
### Example
|
|
86
85
|
|
|
@@ -91,3 +90,103 @@ An example deployable with [claudia.js](https://claudiajs.com/tutorials/serverle
|
|
|
91
90
|
|
|
92
91
|
- API Gateway doesn't support streams yet, so you're not able to handle [streams](https://www.fastify.io/docs/latest/Reply/#streams).
|
|
93
92
|
- API Gateway has a timeout of 29 seconds, so it's important to provide a reply during this time.
|
|
93
|
+
|
|
94
|
+
## Google Cloud Run
|
|
95
|
+
|
|
96
|
+
Unlike AWS Lambda or Google Cloud Functions, Google Cloud Run is a serverless **container** environment. It's primary purpose is to provide an infrastucture-abstracted environment to run arbitrary containers. As a result, Fastify can be deployed to Google Cloud Run with little-to-no code changes from the way you would write your Fastify app normally.
|
|
97
|
+
|
|
98
|
+
*Follow the steps below to deploy to Google Cloud Run if you are already familiar with gcloud or just follow their [quickstart](https://cloud.google.com/run/docs/quickstarts/build-and-deploy)*
|
|
99
|
+
|
|
100
|
+
### Adjust Fastfiy server
|
|
101
|
+
|
|
102
|
+
In order for Fastify to properly listen for requests within the container, be sure to set the correct port and address:
|
|
103
|
+
|
|
104
|
+
```js
|
|
105
|
+
function build() {
|
|
106
|
+
const fastify = Fastify({ trustProxy: true })
|
|
107
|
+
return fastify
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function start() {
|
|
111
|
+
// Google Cloud Run will set this environment variable for you, so
|
|
112
|
+
// you can also use it to detect if you are running in Cloud Run
|
|
113
|
+
const IS_GOOGLE_CLOUD_RUN = process.env.K_SERVICE !== undefined
|
|
114
|
+
|
|
115
|
+
// You must listen on the port Cloud Run provides
|
|
116
|
+
const port = process.env.PORT || 3000
|
|
117
|
+
|
|
118
|
+
// You must listen on all IPV4 addresses in Cloud Run
|
|
119
|
+
const address = IS_GOOGLE_CLOUD_RUN ? "0.0.0.0" : undefined
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const server = build()
|
|
123
|
+
const address = await server.listen(port, address)
|
|
124
|
+
console.log(`Listening on ${address}`)
|
|
125
|
+
} catch (err) {
|
|
126
|
+
console.error(err)
|
|
127
|
+
process.exit(1)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
module.exports = build
|
|
132
|
+
|
|
133
|
+
if (require.main === module) {
|
|
134
|
+
start()
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Add a Dockerfile
|
|
139
|
+
|
|
140
|
+
You can add any valid `Dockerfile` that packages and runs a Node app. A basic `Dockerfile` can be found in the official [gcloud docs](https://github.com/knative/docs/blob/2d654d1fd6311750cc57187a86253c52f273d924/docs/serving/samples/hello-world/helloworld-nodejs/Dockerfile).
|
|
141
|
+
|
|
142
|
+
```Dockerfile
|
|
143
|
+
# Use the official Node.js 10 image.
|
|
144
|
+
# https://hub.docker.com/_/node
|
|
145
|
+
FROM node:10
|
|
146
|
+
|
|
147
|
+
# Create and change to the app directory.
|
|
148
|
+
WORKDIR /usr/src/app
|
|
149
|
+
|
|
150
|
+
# Copy application dependency manifests to the container image.
|
|
151
|
+
# A wildcard is used to ensure both package.json AND package-lock.json are copied.
|
|
152
|
+
# Copying this separately prevents re-running npm install on every code change.
|
|
153
|
+
COPY package*.json ./
|
|
154
|
+
|
|
155
|
+
# Install production dependencies.
|
|
156
|
+
RUN npm install --only=production
|
|
157
|
+
|
|
158
|
+
# Copy local code to the container image.
|
|
159
|
+
COPY . .
|
|
160
|
+
|
|
161
|
+
# Run the web service on container startup.
|
|
162
|
+
CMD [ "npm", "start" ]
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Add a .dockerignore
|
|
166
|
+
|
|
167
|
+
To keep build artifacts out of your container (which keeps it small and improves build times), add a `.dockerignore` file like the one below:
|
|
168
|
+
|
|
169
|
+
```.dockerignore
|
|
170
|
+
Dockerfile
|
|
171
|
+
README.md
|
|
172
|
+
node_modules
|
|
173
|
+
npm-debug.log
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Submit build
|
|
177
|
+
|
|
178
|
+
Next, submit your app to be built into a Docker image by running the following command (replacing `PROJECT-ID` and `APP-NAME` with your GCP project id and an app name:
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
gcloud builds submit --tag gcr.io/PROJECT-ID/APP-NAME
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Deploy Image
|
|
185
|
+
|
|
186
|
+
After your image has built, you can deploy it with the following command:
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
gcloud beta run deploy --image gcr.io/PROJECT-ID/APP-NAME --platform managed
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Your app will be accessible from the URL GCP provides.
|
package/fastify.d.ts
CHANGED
package/lib/handleRequest.js
CHANGED
|
@@ -72,7 +72,7 @@ function handler (request, reply) {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
function preValidationCallback (err, request, reply) {
|
|
75
|
-
if (reply.res.finished === true) return
|
|
75
|
+
if (reply.sent === true || reply.res.finished === true) return
|
|
76
76
|
if (err != null) {
|
|
77
77
|
reply.send(err)
|
|
78
78
|
return
|
|
@@ -103,7 +103,7 @@ function preValidationCallback (err, request, reply) {
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
function preHandlerCallback (err, request, reply) {
|
|
106
|
-
if (reply.res.finished === true) return
|
|
106
|
+
if (reply.sent || reply.res.finished === true) return
|
|
107
107
|
if (err != null) {
|
|
108
108
|
reply.send(err)
|
|
109
109
|
return
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fastify",
|
|
3
|
-
"version": "2.7.
|
|
3
|
+
"version": "2.7.1",
|
|
4
4
|
"description": "Fast and low overhead web framework, for Node.js",
|
|
5
5
|
"main": "fastify.js",
|
|
6
6
|
"typings": "fastify.d.ts",
|
|
@@ -88,25 +88,25 @@
|
|
|
88
88
|
"node": ">=6"
|
|
89
89
|
},
|
|
90
90
|
"devDependencies": {
|
|
91
|
-
"@types/node": "^11.13.
|
|
92
|
-
"@typescript-eslint/eslint-plugin": "^1.
|
|
93
|
-
"@typescript-eslint/parser": "^1.
|
|
91
|
+
"@types/node": "^11.13.18",
|
|
92
|
+
"@typescript-eslint/eslint-plugin": "^1.13.0",
|
|
93
|
+
"@typescript-eslint/parser": "^1.13.0",
|
|
94
94
|
"JSONStream": "^1.3.5",
|
|
95
95
|
"ajv-pack": "^0.3.1",
|
|
96
96
|
"autocannon": "^3.2.0",
|
|
97
97
|
"branch-comparer": "^0.4.0",
|
|
98
|
-
"concurrently": "^4.1.
|
|
98
|
+
"concurrently": "^4.1.1",
|
|
99
99
|
"cors": "^2.8.5",
|
|
100
|
-
"coveralls": "^3.0.
|
|
100
|
+
"coveralls": "^3.0.5",
|
|
101
101
|
"dns-prefetch-control": "^0.2.0",
|
|
102
102
|
"eslint-import-resolver-node": "^0.3.2",
|
|
103
103
|
"fast-json-body": "^1.1.0",
|
|
104
104
|
"fastify-plugin": "^1.5.0",
|
|
105
105
|
"fluent-schema": "^0.7.3",
|
|
106
|
-
"form-data": "^2.
|
|
106
|
+
"form-data": "^2.5.0",
|
|
107
107
|
"frameguard": "^3.0.0",
|
|
108
108
|
"h2url": "^0.1.2",
|
|
109
|
-
"helmet": "^3.
|
|
109
|
+
"helmet": "^3.20.0",
|
|
110
110
|
"hide-powered-by": "^1.0.0",
|
|
111
111
|
"hsts": "^2.1.0",
|
|
112
112
|
"http-errors": "^1.7.1",
|
|
@@ -115,9 +115,9 @@
|
|
|
115
115
|
"license-checker": "^25.0.1",
|
|
116
116
|
"lolex": "^4.0.1",
|
|
117
117
|
"pre-commit": "^1.2.2",
|
|
118
|
-
"proxyquire": "^2.1.
|
|
118
|
+
"proxyquire": "^2.1.1",
|
|
119
119
|
"pump": "^3.0.0",
|
|
120
|
-
"semver": "^6.
|
|
120
|
+
"semver": "^6.3.0",
|
|
121
121
|
"send": "^0.17.0",
|
|
122
122
|
"serve-static": "^1.13.2",
|
|
123
123
|
"simple-get": "^3.0.3",
|
|
@@ -127,19 +127,19 @@
|
|
|
127
127
|
"tap": "^12.5.2",
|
|
128
128
|
"tap-mocha-reporter": "^3.0.7",
|
|
129
129
|
"then-sleep": "^1.0.1",
|
|
130
|
-
"typescript": "^3.5.
|
|
130
|
+
"typescript": "^3.5.3",
|
|
131
131
|
"x-xss-protection": "^1.1.0"
|
|
132
132
|
},
|
|
133
133
|
"dependencies": {
|
|
134
134
|
"abstract-logging": "^1.0.0",
|
|
135
|
-
"ajv": "^6.
|
|
135
|
+
"ajv": "^6.10.2",
|
|
136
136
|
"avvio": "^6.1.1",
|
|
137
137
|
"fast-json-stringify": "^1.15.0",
|
|
138
138
|
"find-my-way": "^2.0.0",
|
|
139
139
|
"flatstr": "^1.0.12",
|
|
140
|
-
"light-my-request": "^3.
|
|
140
|
+
"light-my-request": "^3.4.1",
|
|
141
141
|
"middie": "^4.0.1",
|
|
142
|
-
"pino": "^5.
|
|
142
|
+
"pino": "^5.13.1",
|
|
143
143
|
"proxy-addr": "^2.0.4",
|
|
144
144
|
"readable-stream": "^3.1.1",
|
|
145
145
|
"rfdc": "^1.1.2",
|
package/test/hooks-async.js
CHANGED
|
@@ -4,7 +4,8 @@ const split = require('split2')
|
|
|
4
4
|
const sget = require('simple-get').concat
|
|
5
5
|
const Fastify = require('..')
|
|
6
6
|
const fs = require('fs')
|
|
7
|
-
const
|
|
7
|
+
const { promisify } = require('util')
|
|
8
|
+
const sleep = promisify(setTimeout)
|
|
8
9
|
|
|
9
10
|
function asyncHookTest (t) {
|
|
10
11
|
const test = t.test
|
|
@@ -231,6 +232,53 @@ function asyncHookTest (t) {
|
|
|
231
232
|
})
|
|
232
233
|
})
|
|
233
234
|
|
|
235
|
+
test('preValidation hooks should be able to block a request with async onSend', t => {
|
|
236
|
+
t.plan(5)
|
|
237
|
+
const fastify = Fastify()
|
|
238
|
+
|
|
239
|
+
fastify.addHook('preValidation', async (req, reply) => {
|
|
240
|
+
reply.send('hello')
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
fastify.addHook('onSend', async (req, reply, payload) => {
|
|
244
|
+
await sleep(10)
|
|
245
|
+
t.equal(payload, 'hello')
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
fastify.addHook('preHandler', async (request, reply) => {
|
|
249
|
+
t.fail('we should not be here')
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
fastify.addHook('onResponse', async (request, reply) => {
|
|
253
|
+
t.ok('called')
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
fastify.post('/', {
|
|
257
|
+
schema: {
|
|
258
|
+
body: {
|
|
259
|
+
type: 'object',
|
|
260
|
+
properties: {
|
|
261
|
+
hello: {
|
|
262
|
+
type: 'string'
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
required: ['hello']
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}, function (request, reply) {
|
|
269
|
+
t.fail('we should not be here')
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
fastify.inject({
|
|
273
|
+
url: '/',
|
|
274
|
+
method: 'POST'
|
|
275
|
+
}, (err, res) => {
|
|
276
|
+
t.error(err)
|
|
277
|
+
t.is(res.statusCode, 200)
|
|
278
|
+
t.is(res.payload, 'hello')
|
|
279
|
+
})
|
|
280
|
+
})
|
|
281
|
+
|
|
234
282
|
test('preSerialization hooks should be able to modify the payload', t => {
|
|
235
283
|
t.plan(3)
|
|
236
284
|
const fastify = Fastify()
|
|
@@ -339,6 +387,37 @@ function asyncHookTest (t) {
|
|
|
339
387
|
})
|
|
340
388
|
})
|
|
341
389
|
|
|
390
|
+
test('preHandler hooks should be able to block a request (last hook) with a delay in onSend', t => {
|
|
391
|
+
t.plan(5)
|
|
392
|
+
const fastify = Fastify()
|
|
393
|
+
|
|
394
|
+
fastify.addHook('preHandler', async (req, reply) => {
|
|
395
|
+
reply.send('hello')
|
|
396
|
+
})
|
|
397
|
+
|
|
398
|
+
fastify.addHook('onSend', async (req, reply, payload) => {
|
|
399
|
+
await sleep(10)
|
|
400
|
+
t.equal(payload, 'hello')
|
|
401
|
+
})
|
|
402
|
+
|
|
403
|
+
fastify.addHook('onResponse', async (request, reply) => {
|
|
404
|
+
t.ok('called')
|
|
405
|
+
})
|
|
406
|
+
|
|
407
|
+
fastify.get('/', function (request, reply) {
|
|
408
|
+
t.fail('we should not be here')
|
|
409
|
+
})
|
|
410
|
+
|
|
411
|
+
fastify.inject({
|
|
412
|
+
url: '/',
|
|
413
|
+
method: 'GET'
|
|
414
|
+
}, (err, res) => {
|
|
415
|
+
t.error(err)
|
|
416
|
+
t.is(res.statusCode, 200)
|
|
417
|
+
t.is(res.payload, 'hello')
|
|
418
|
+
})
|
|
419
|
+
})
|
|
420
|
+
|
|
342
421
|
test('onRequest respond with a stream', t => {
|
|
343
422
|
t.plan(4)
|
|
344
423
|
const fastify = Fastify()
|