High performance web application practices
introduce
fibjs is a high-performance application server framework designed primarily for web backend development. It is built on the Google v8 JavaScript engine and chooses a different concurrency solution than traditional callbacks. fibjs uses fibers to isolate the business complexity caused by asynchronous calls at the framework layer, greatly reducing the difficulty of development and reducing performance problems caused by frequent asynchronous processing in user space.
The design of fibjs pays great attention to performance. The built-in network IO and HTTP modules adopt an event-driven non-blocking I/O model, so developers can easily implement high-reliability server applications. And because the bottom layer is implemented in C++, fibjs has very superior performance and can easily cope with high concurrent access and provide extremely stable and reliable services.
At the same time, fibjs also supports WebSocket, which is a full-duplex communication protocol based on the TCP protocol. It establishes an uninterrupted connection between the browser and the server, enables real-time two-way data transmission, and can support data in any format. transmission. WebSocket can be used to easily implement real-time communication applications with better communication effects.
In short, fibjs not only emphasizes high performance and high reliability, but also provides real-time communication features such as WebSocket. It is a framework that is very suitable for developing high-speed Web applications.
Development environment setup
Before starting the development of fibjs, we need to prepare the development environment. This chapter will introduce how to install fibjs, how to use the fibjs tool to initialize the project, and how to use the IDE integrated development environment.
Install fibjs
For different operating systems, the methods of installing fibjs are slightly different.
For Linux and macOS users, fibjs can be installed using the following command:
1curl -s https://fibjs.org/download/installer.sh | sh
If you are using macOS and using the Homebrew package manager, you can also install it using the following command:
1brew install fibjs
For Windows users, you need to download the installer from the fibjs official website and then follow the instructions to install it.
Create a new project using fibjs –init
After installing fibjs, you can use the fibjs tool to quickly create new projects. Use the following command to create a basic project template:
1fibjs --init
This command will create a new project structure in the current directory, including package.json, which is used to store basic information of the project and dependency information.
Writing web applications
Web application development is currently the most commonly used application scenario of fibjs. fibjs provides a series of tools and modules to help us build Web applications more quickly.
Writing an HTTP server
- First import the http module;
- Instantiate http.Server and listen for requests.
- The server is started through the start function.
1
2
3
4
5
6const http = require('http');
const server = new http.Server(8080, (req) => {
req.response.write('Hello World!');
});
server.start();
Parse URL parameters and request body
Parsing url parameters and request bodies is very important and is used in various server-side applications. In fibjs, the incoming url parameters can be parsed directly through req.query, and the request body is read through req.body.
1
2
3
4
5
6
7
8const http = require('http');
const server = new http.Server(8080, (req) => {
var name = req.query.get('name');
var msg = name ? `Hello ${name}!` : 'Hello world!';
req.response.write(msg);
});
server.start();
Implement interface access control
Restricting user access through interfaces is a very common scenario. Below is a simple example.
1
2
3
4
5
6
7
8
9
10const http = require('http');
const server = new http.Server(8080, (req) => {
if (req.headers.get('auth') === 'ok') {
req.response.write('Hello World!');
} else {
req.response.write('Access Denied!');
}
});
server.start();
Add routing processing
Routing is one of the most important concepts in a Web application. Routing refers to distributing received requests to processors according to certain rules. In fibjs, you can write your own routing module and bind it to the http server, and then perform URL matching and corresponding processing through customized route parsing.
1
2
3
4
5
6
7
8
9const http = require('http');
const { Router } = require('mq');
var router = new Router();
router.get('/hello/:name', function (req, name) {
req.response.write('hello, ' + name);
});
var svr = new http.Server(8080, router);
svr.start();
The above example can also be implemented with simpler syntax:
1
2
3
4
5
6
7
8const http = require('http');
var svr = new http.Server(8080, {
'/hello/:name': function (req, name) {
req.response.write('hello, ' + name);
}
});
svr.start();
Error handling and logging
In fibjs, you can capture logical exceptions through try-catch blocks and output them to log files for debugging and recording; if they are fatal exceptions, they can be thrown directly to the upper framework for processing.
1
2
3
4
5
6
7
8
9
10const console = require('console');
const http = require('http');
const server = new http.Server(8080, (req) => {
try {
// ...
} catch (e) {
console.log(e.message, e.stack);
}
});
cross domain request
In fibjs, we can use the enableCrossOrigin method to allow cross-domain requests. Here is a sample code of how to create an http server and allow cross-domain requests:
1
2
3
4
5
6
7
8const http = require('http');
const server = new http.Server(8080, (req) => {
req.response.write('Hello World!');
});
server.enableCrossOrigin(); // enable cross domain request
server.start();
In the above example, we created an http server with port 8080. The enableCrossOrigin() method allows cross-origin requests.
When using enableCrossOrigin to allow cross-domain requests, you can specify the cross-domain headers that are allowed to be received by passing a parameter allowHeaders. By default, the allowHeaders value is Content-Type.
The sample code is as follows:
1
2// enable "Content-Type" and "Authorization" headers in cross domain request
server.enableCrossOrigin("Content-Type, Authorization");
In the above code, the value of allowHeaders is "Content-Type, Authorization", which means that the server is allowed to receive the two cross-domain headers "Content-Type" and "Authorization". If the request contains other headers, it will be rejected by the server.
It should be noted that when we use enableCrossOrigin to set up the ability to receive cross-domain headers, we also need to set the corresponding request header when sending cross-domain requests, otherwise it will also be rejected by the server.
WebSockets
The WebSocket protocol is a full-duplex communication protocol based on the TCP protocol. It establishes an uninterrupted connection between the browser and the server, can realize real-time two-way data transmission, and can support data transmission in any format. In fibjs, the WebSocket support module provides corresponding API interfaces, which can realize the development of WebSocket server and client.
Use fibjs native WebSocket module to implement WebSocket server side
On the server side, HTTP requests can be converted into WebSocket connections through the upgrade function. When creating an http server object, you can use ws.upgrade(callback) and pass it into the http.start() method to convert the http request to WebSocket.
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
27var ws = require('ws');
var http = require('http');
var server = new http.Server(8080, {
'/ws': ws.upgrade(function(conn, req) {
console.log('a client connected.');
// listening for message events
conn.onmessage = function(evt) {
console.log('received message: ', evt.data);
// echo the message back to client
conn.send('Server: ' + evt.data);
};
// listening for close events
conn.onclose = function(code, reason) {
console.log('closed.');
};
// listening for error events
conn.onerror = function(err) {
console.log(err);
};
})
});
server.start();
In the above example, we can monitor the message event sent by the client and the connection closing event between the server and the client. When the server receives the client message, it sends the same message back to the client. At this point simple WebSocket point-to-point communication is implemented.
Implement interaction with data storage
When using WebSocket for communication, in addition to simply sending and receiving messages, you also need to consider operations such as persistent storage and query of data. At this time, you need to use the database. You can use the db module built in fibjs to interact with the database.
The sample code is as follows:
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
33var ws = require("ws");
var http = require("http");
var db = require("db");
// open a mysql connection
var mysql = db.openMySQL("mysql://root:password@localhost/dbname");
var server = new http.Server(8080, {
"/ws": ws.upgrade(function(conn, req) {
console.log("a client connected.");
// listening for message events
conn.onmessage = function(evt) {
console.log("received message: ", evt.data);
// use execute to query the data
var rs = mysql.execute("SELECT * FROM user WHERE name=?", evt.data.toString());
conn.send(JSON.stringify(rs));
};
// listening for close events
conn.onclose = function(code, reason) {
console.log("closed.");
};
// listening for error events
conn.onerror = function(err) {
console.log(err);
};
})
});
server.start();
In the above example, we first used the openMySQL method of the db module to create a MySQL database connection object mysql, and then after listening to the message from the client, we used the execute method to directly execute the SQL query and obtain records that meet the conditions. Finally, the query results are sent back to the client through the WebSocket protocol.
It should be noted that in actual development, exception handling and data security need to be done well.
To sum up, through the db module, we can easily and easily interact with the database, combined with the WebSocket protocol, to implement real-time, high-performance Web applications.
Implement WebSocket client-server communication
On the client side, you can connect to a WebSocket server by creating a WebSocket instance and specifying a URL, and then send messages to the server.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24var ws = require('ws');
// create a WebSocket object and connect to ws://localhost:8080/ws
var conn = new ws.Socket('ws://localhost:8080/ws');
// listening for open events
conn.onopen = function() {
conn.send('hello');
}
// listening for message events
conn.onmessage = function(evt) {
console.log('received message:', evt.data);
}
// listening for close events
conn.onclose = function(code, reason) {
console.log('closed.');
}
// listening for error events
conn.onerror = function(err) {
console.log(err);
}
In the above client code, we create a WebSocket instance and specify its URL. After the connection is successfully established, we can send messages to the server. When the server receives the client message, it sends the same message back to the client. At this point, simple WebSocket point-to-point communication is implemented.
Advantages and usage scenarios of WebSocket
The WebSocket protocol has a typical two-way communication model, allowing the server to actively push data to the client. It is often used to implement chat, online games and other occasions that require real-time and high immediacy. Compared with other transmission protocols, the WebSocket protocol has the following advantages:
• High real-time performance, supports two-way communication • Simple protocol specification, easy to use • Able to handle a large number of concurrent connections • Supports long connections, reducing network transmission time
The most common usage scenarios of WebSocket include web chat, game battles, online playback, and instant messaging.
To sum up, through the WebSocket support module, it is very simple to implement, and developers can quickly build their own Web applications.
unit test
Testing frameworks and testing methods
In the software development process, testing is a very important link, and unit testing is an important part of it. Unit testing can effectively verify whether the code meets the design and requirements, and avoid errors introduced when the code is modified. Generally speaking, the principle of unit testing is to test each function and method to ensure that the input and output of each function and method are correct.
A test framework is a code base used to write, run and verify test cases. It provides test case management, running and reporting functions. In JavaScript and Node.js, popular unit testing frameworks include Mocha, Jest, and Jasmine. In fibjs, we also have our own testing framework, the test module.
In the unit testing process, the commonly used testing methods include black box testing and white box testing.
Black box testing is a testing method that only considers the input and output of a function without considering the implementation details inside the function. Black box testing is based on requirement analysis and design specifications. Through test case analysis and execution, it determines whether the program has logic errors, boundary errors, security issues, etc. Its advantage is that the test process is simple and the test results are reliable. Its disadvantage is that the test cannot cover all program paths.
White-box testing is a testing method that considers the internal implementation details of a function, including conditional statements, loop statements, recursion, and code coverage. These tests can identify possible problems in the interaction between shared data and code. The advantage of white-box testing is that it can cover all program paths. The disadvantage is that the testing process is more complicated and the test results are affected by the environment and implementation methods.
Write test cases using the test module
In fibjs, we can use the test module to write test cases for the web server. Here's a simple example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14var test = require('test');
test.setup();
var http = require('http');
describe('Web server test', () => {
it('should return hello world', () => {
var r = http.get('http://localhost:8080/hello');
assert.equal(r.statusCode, 200);
assert.equal(r.data.toString(), 'Hello World');
});
});
test.run();
In this example, we use the describe and it functions to define the test module and test case respectively, and use the assert function for assertion verification.
In the describe function, we can define multiple it functions to test different scenarios respectively. In each it function, we can use the http.get function to simulate an HTTP GET request, obtain the request response and perform assertion verification such as assertTrue and assertEqual.
By writing test cases, you can effectively test the correctness of functions and modules, ensure product quality, and also enhance the maintainability of the code.
Hot update
Hot update refers to updating the server code without stopping the service. During the program development process, in order to quickly iterate, code adjustments and new functions are often required. Using hot update, you can use new code to complete iteration work more efficiently without stopping the service.
In fibjs, we can use the SandBox module to achieve smooth hot updates. The SandBox module can provide a safe execution environment and simulate global variables and other functions. For specific implementation, please refer to the following steps:
- Load the code files that need to be updated (such as web.js).
- Through SandBox, create a new security module, load web.js in the module, and generate the security module. Remount the handler of the running service through the generated security module.
- The server continues to process previous requests, and new requests will be mounted on the new handler.
The following is a sample code that uses the SandBox module to implement smooth hot updates:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16const fs = require('fs');
const http = require('http');
const { SandBox } = require('vm');
let FILE_PATH = './web.js';
let handler = new SandBox().require(FILE_PATH).handler;
const server = new http.Server(8080, handler);
server.start();
fs.watch(FILE_PATH, (event, filename) => {
handler = new SandBox().require(FILE_PATH).handler;
server.handler = handler;
console.log(`[${new Date().toLocaleString()}] server reloaded.`);
});
In this code, we first load the code in web.js when the program starts, then create a SandBox instance and load the code in the instance. After that, we created a new HTTP Server and used the methods in the handler to process the request.
In the code, we use fs.watch to monitor changes in the web.js file. Once the file changes, we reload the code and update the implementation in the handler.
Performance optimization
During the development process, we often need to face performance issues. Optimizing code and improving performance are one of the essential skills for developers. In fibjs, we can use CPU Profiler to help us analyze the running status of the program and optimize the code.
In fibjs, you only need to use the command line parameter --prof to start fibjs to start the CPU Profiler (the default interval is 1000ms). If you need higher precision log analysis, you can use the --prof-interval parameter to set the log interval. For example:
1
2$ fibjs --prof test.js # 启动 CPU Profiler,默认以 1000ms 为间隔
$ fibjs --prof --prof-interval=10ms test.js # 启动 CPU Profiler,以 10000us(即 10ms)为间隔
When fibjs is finished running, a source file name directory will be generated in the current directory. This directory contains a log file and some auxiliary files. The default name of the log file is fibjs-xxxx.log, where xxxx is a timestamp. You can use the --log option to specify the log file name. At this point, you can --prof-process
process the generated logs using:
1fibjs --prof-process fibjs-xxxx.log prof.svg
After the operation is completed, use the browser to open prof.svg to view the flame graph of this log: you can click to view the full-size image. In the full-size image, you can operate the mouse to view more detailed information: prof. svg .
In the generated flame graph, each color block represents a recording point. The longer the color block, the more times it is recorded; each line represents a layer of call stack, and the more layers, the more layers are called; calls The stack is placed upside down. The lower the color block, the more original the function.
There are two types of color blocks, one is red and the other is blue. In the fibjs profiler, red represents JavaScript operations, and blue represents io operations or Native operations. Depending on the problem you need to solve, the areas you need to focus on will vary. For example, if you need to solve the problem of high CPU usage, you need to pay attention to the red color blocks. If your application has low CPU usage but slow response, you need to pay attention to the blue color blocks. The larger the color block near the top, the more attention and optimization it needs.
We can try to adjust functions that take up more CPU resources, implement IO etc. asynchronously, or optimize the code when writing.
Deployment and online
In order for our project to run in a production environment, we need to compile and deploy it. Here we introduce how to use the package.json file to configure compilation and deployment.
In the project, we can use package.json to manage project dependencies, configure compilation and deployment. Take a simple sample package.json as an example:
1
2
3
4
5
6
7{
"name": "my-project",
"version": "1.0.0",
"dependencies": {
"fib-pool": "^1.0.0"
}
}
When we need to compile and deploy the project, we only need to enter the project directory in the terminal and execute the following command:
1fibjs --install
This command will automatically install the modules that the project depends on. Afterwards, we can start the project using the following command:
1fibjs app.js