preface
Those of you who have developed with Node.js will be familiar with KOA because of its simple and elegant writing style, the rich community ecology, and the fact that many existing Node.js frameworks are repackaged based on KOA. However, in terms of performance, it is important to mention a well-known framework, Fastify, which, by its name, is characteristically fast, and official Benchmarks suggest it is even faster than Node.js’s native HTTP.server, too.
Key to performance improvement
Let’s start by looking at how Fastify launches a service.
# installation fastifyNPM I - S [email protected]Copy the code
// Create a service instance
const fastify = require('fastify')()
app.get('/', {
schema: {
response: {
// Key indicates the response status code
'200': {
type: 'object'.properties: {
hello: { type: 'string'}}}}}},async() = > {return { hello: 'world'}})// Start the service; (async() = > {try {
const port = 3001 // Listen on the port
await app.listen(port)
console.info(`server listening on ${port}`)}catch (err) {
console.error(err)
process.exit(1)}}) ()Copy the code
As you can see from the above code, Fastify defines a schema for the response body of a request. In addition to defining the schema for the response body, Fastify also supports defining the following schemas for data:
body
: Validates the request body when the POST or PUT method is used.query
: verifies url query parameters;params
: verify URL parameters;response
: filters and generates for the response bodyschema
.
app.post('/user/:id', {
schema: {
params: {
type: 'object'.properties: {
id: { type: 'number'}}},response: {
// 2xx indicates that this schema applies to states from 200 to 299
'2xx': {
type: 'object'.properties: {
id: { type: 'number' },
name: { type: 'string'}}}}}},async (req) => {
const id = req.params.id
const userInfo = await User.findById(id)
// Content-type Defaults to application/json
return userInfo
})
Copy the code
The secret to fastify’s improved performance is that instead of using the native Json.stringify, it reimplements its own json serialization method. This schema is the key to doubling the performance of JSON serialization.
How do I serialize JSON
Before exploring how Fastify serializes JSON data, let’s take a look at how tedious json.stringify goes through, Here we refer to the Stringify method implemented in the open source JSON-JS by Douglas Crockford (creator of the JSON format).
JSON-js:github.com/douglascroc…
// show only the core json.stringify code, other code omitted
if (typeof JSON! = ="object") {
JSON = {};
}
JSON.stringify = function (value) {
return str("", {"": value})
}
function str(key, holder) {
var value = holder[key];
switch(typeof value) {
case "string":
return quote(value);
case "number":
return (isFinite(value)) ? String(value) : "null";
case "boolean":
case "null":
return String(value);
case "object":
if(! value) {return "null";
}
partial = [];
if (Object.prototype.toString.apply(value) === "[object Array]") {
// Handle arrays
length = value.length;
for (i = 0; i < length; i += 1) {
// Each element needs to be handled separately
partial[i] = str(i, value) || "null";
}
// Convert partial to "[...] "
v = partial.length === 0
? "[]"
: "[" + partial.join(",") + "]";
return v;
} else {
// Process objects
for (k in value) {
if (Object.prototype.hasOwnProperty.call(value, k)) {
v = str(k, value);
if (v) {
partial.push(quote(k) + ":"+ v); }}}// Convert partial to "{... }"
v = partial.length === 0
? "{}"
: "{" + partial.join(",") + "}";
returnv; }}}Copy the code
As can be seen from the above code, when serializing JSON objects, it is necessary to traverse all arrays and objects, judge the type one by one, and add “” to all keys, and it does not include some encode operations of special characters. However, with schema, these situations become much easier. Fastify officially makes JSON serialization a separate repository: fast-jSON-stringify, and later introduces AJV for validation. Here, in order to make it easier to read the code, we choose to look at the earlier version: 0.1.0, which is relatively simple logic and easy to understand.
[email protected]: github.com/fastify/fas…
function $Null (i) {
return 'null'
}
function $Number (i) {
var num = Number(i)
if (isNaN(num)) {
return 'null'
} else {
return String(num)
}
}
function $String (i) {
return '"' + i + '"'
}
function buildObject (schema, code, name) {
// Serialize objects...
}
function buildArray (schema, code, name) {
// Serialize array...
}
function build (schema) {
var code = `
'use strict'
The ${$String.toString()}
The ${$Number.toString()}
${$Null.toString()}
`
var main
code = buildObject(schema, code, '$main')
code += `; return $main `
return (new Function(code))()
}
module.exports = build
Copy the code
Fast-json-stringify exposes a build method that takes a schema and returns a function ($main) that serializes the objects corresponding to the schema as follows:
const build = require('fast-json-stringify')
const stringify = build({
type: 'object'.properties: {
id: { type: 'number' },
name: { type: 'string'}}})console.log(stringify)
const objString = stringify({
id: 1.name: 'shenfq'
})
console.log(objString) // {"id":1,"name":"shenfq"}
Copy the code
After the build construct, the serialization method returned is as follows:
function $String (i) {
return '"' + i + '"'
}
function $Number (i) {
var num = Number(i)
if (isNaN(num)) {
return 'null'
} else {
return String(num)
}
}
function $Null (i) {
return 'null'
}
// Serialization method
function $main (obj) {
var json = '{'
json += '"id":'
json += $Number(obj.id)
json += ', '
json += '"name":'
json += $String(obj.name)
json += '} '
return json
}
Copy the code
As you can see, with schema as the support, the serialization logic suddenly becomes extremely simple, and the resulting JSON string only retains the required attributes, which is concise and efficient. Let’s go back and look at how buildObject generates the code inside $main:
function buildObject (schema, code, name) {
// Construct a function
code += `
function ${name} (obj) {
var json = '{'
`
var laterCode = ' '
// Iterate over the schema properties
const { properties } = schema
Object.keys(properties).forEach((key, i, a) = > {
// The key needs double quotation marks
code += `
json += 'The ${$String(key)}:'
`
// Transform value by nested
const value = properties[key]
const result = nested(laterCode, name, `.${key}`, value)
code += result.code
laterCode = result.laterCode
if (i < a.length - 1) {
code += 'json += \',\''
}
})
code += ` json += '}' return json } `
code += laterCode
return code
}
function nested (laterCode, name, key, schema) {
var code = ' '
var funcName
// Determine the type of value
const type = schema.type
switch (type) {
case 'null':
code += ` json += $Null() `
break
case 'string':
code += `
json += $String(obj${key})
`
break
case 'number':
case 'integer':
code += `
json += $Number(obj${key})
`
break
case 'object':
If value is an object, a new method is required to construct it
funcName = (name + key).replace(/[-.\[\]]/g.' ')
laterCode = buildObject(schema, laterCode, funcName)
code += `
json += ${funcName}(obj${key})
`
break
case 'array':
funcName = (name + key).replace(/[-.\[\]]/g.' ')
laterCode = buildArray(schema, laterCode, funcName)
code += `
json += ${funcName}(obj${key})
`
break
default:
throw new Error(`${type} unsupported`)}return {
code,
laterCode
}
}
Copy the code
Properties of type “object” are traversed once, and then processed twice for different types of value. If a new object is encountered, a new function is constructed to handle it.
// If it contains child objects
const stringify = build({
type: 'object'.properties: {
id: { type: 'number' },
info: {
type: 'object'.properties: {
age: { type: 'number' },
name: { type: 'string'},}}}})console.log(stringify.toString())
Copy the code
function $main (obj) {
var json = '{'
json += '"id":'
json += $Number(obj.id)
json += ', '
json += '"info":'
json += $maininfo(obj.info)
json += '} '
return json
}
// Child objects are processed by another function
function $maininfo (obj) {
var json = '{'
json += '"age":'
json += $Number(obj.age)
json += ', '
json += '"name":'
json += $String(obj.name)
json += '} '
return json
}
Copy the code
conclusion
Of course, there are other internal optimizations that fastify claims to be fast, such as the use of Radix Tree for the implementation of the routing library and the reuse of context objects (using the MidDIE library). This article just introduces one of them reflects the most important obvious optimization ideas, I hope you can gain after reading.