Home > Article > Web Front-end > Server-side JavaScript programming with node.js
Simply put, node.js is a framework that allows developers to write server-side code using the JavaScript language. In other words, the JavaScript code written can run directly on the local machine, not just the browser. From an implementation perspective, both Jaxer and node.js use existing JavaScript execution engines. Jaxer uses the JavaScript engine used in Mozilla Firefox, while node.js uses the V8 engine used in Google Chrome.
Getting started with node.js
node.js can run on mainstream operating systems such as Linux, Windows and Macintosh. If you run node.js on the Windows platform, you need the support of Cygwin or MinGW. The following uses the commonly used Windows platform as an example to illustrate. First you need to install Cygwin. When installing, you need to select gcc-g++, make, openssl and python packages. The version of gcc must be the latest. Then download the source code of node.js version 0.4.0 from the address given in Resources. After downloading and decompressing, run commands such as ./configure, make and make install in Cygwin in order to compile and install. After the installation is complete, run the node command directly to start the command line provided by node.js. JavaScript code can be entered directly into the command line and run. You can also run a JavaScript file server.js through node server.js.
An example of a simple "Hello World" program is given in Code Listing 1. After running the JavaScript file through node helloworld.js, "Hello World" will be output on the console.
Listing 1. "Hello World" program using node.js
process.stdout.write("Hello World");
The process in the code list 1 represents the currently running node.js process, and its attributes stdout represents the standard output stream of the process. Write a string to the stream using the write() method. As you can see from Code Listing 1, you can use JavaScript to access resources on the local system such as the standard output stream. This reflects the power of node.js from one side.
In JavaScript code that can be run by node.js, you can use some global objects: including the process used in code listing 1, the require() method used to load modules introduced below, and the JavaScript currently being executed. __filename of the file name, __dirname indicating the directory of the currently executing JavaScript file, and setTimeout() and setInterval() methods similar to those used in browsers for executing scheduled tasks.
After introducing the basic knowledge of node.js, let’s introduce the modular structure of node.js.
Back to top
Modular structure
node.js uses the module system defined by CommonJS. Different functional components are divided into different modules. Applications can choose to use appropriate modules according to their own needs. Each module exposes some public methods or properties. Module users can use these methods or properties directly without having to worry about the implementation details inside the module. In addition to the multiple modules preset by the system, the application development team can also use this mechanism to split the application into multiple modules to improve code reusability.
Using Modules
Using a module in node.js is very simple. Before using a module, you need to declare its dependency on it. You can directly use the global function require() in JavaScript code to load a module. For example, require("http") can load the system's preset http module. And require("./myModule.js") is used to load the myModule.js module in the same directory as the current JavaScript file. If the path using require() starts with "/", it is considered to be the absolute path of the module JavaScript file on the operating system. If neither of these is the case, node.js will try to look in the node_modules directory under the parent directory of the current JavaScript file and its ancestor directories. For example, if require("other.js") is called in the directory /usr/home/my.js, node.js will try to find the following files in sequence: /usr/home/node_modules/other.js, /usr/node_modules/other .js and /node_modules/other.js. The return value of the
require() method is the public JavaScript object exposed by the module, which contains available methods and properties. Code Listing 2 shows the basic usage of the module.
List 2. Basic usage of the module
var greetings = require("./greetings.js");
var msg = greetings.sayHello("Alex", "zh_CN");
process.stdout.write( msg);
As shown in the code list 2, generally the return value of the require() method is directly assigned to a variable, and this variable can be used directly in the JavaScript code. The greetings.js module exposes a sayHello() method, which is used directly by the current JavaScript code.
Develop your own modules
The basic work of developing your own module is to write the module-related code in the JavaScript file corresponding to the module. This encapsulates the internal processing logic of the module. Generally speaking, a module usually exposes some public methods or properties to its users. The internal code of the module needs to expose these methods or properties. Listing 3 shows the contents of the greetings.js file used in Listing 2.
Listing 3. Contents of greetings.js module
var languages = {
"zh_CN" : "Hello,",
"en" : "Hello, "
};
exports.sayHello = function(name, language ) {
return languages[language] || languages["en"] + name;
};
As shown in code listing 3, the content of the exports object is the return value of the require() method called by the user of the module. What's included. This is how a module declares the public methods and properties it exposes. Variables defined in a module, such as languages , are only visible to code inside the module.
If a module contains a lot of content, it can also be organized in folders. You can create a package.json file under the root directory of the folder, whose content contains the name of the module and the path to the entry JavaScript file. If this package.json file is not provided, node.js will by default look for the index.js file in the folder as the module's startup JavaScript file.
After introducing the modular structure of node.js, let’s introduce its event-driven mechanism.
Back to top
Event-driven
Those who have developed web applications are familiar with the event handling mechanism in browsers. When you are interested in a certain type of event on a certain DOM element, you only need to register an event listener on the DOM element. For example, ele.addEventListener("click", function() {}) adds a listener for the click event. When an event occurs, the event listener's JavaScript method is called. Event processing methods are executed asynchronously. This asynchronous execution method is very suitable for developing high-performance concurrent network applications. In fact, there are generally two approaches to high-performance concurrent application development: the first is to use a multi-threading mechanism, and the other is to use an event-driven approach. The problem with multi-threading is that it is difficult to develop applications, and problems such as thread starvation or deadlock are prone to occur, which puts higher requirements on developers. The event-driven approach is more flexible, easy to understand and use by web developers, and does not have problems such as thread deadlocks. Relying on the powerful Google V8 engine and advanced event I/O architecture, node.js can become a good foundation for creating high-performance server-side applications.
Developing applications based on node.js has a similar programming model to developing web applications. Many modules will expose some events, and the code using these modules can add corresponding processing logic by registering event listeners. Listing 4 shows the implementation code of a simple HTTP proxy server.
List 4. HTTP proxy server
var http = require("http");
var url = require("url");
http.createServer(function (req, res) {
var urlObj = url.parse(req.url, true); // Get the proxy URL
var urlToProxy = urlObj.query.url;
if (!urlToProxy) {
res.statusCode = 400;
res.end("URL is required.");
using use using using using using using using ‐ ‐ ‐ to be res. se( urltoproxy);
varked = {
host: Parsedurl.hostname,
port: PARSEDURL.PORT || 80,
PATH: (Parsedurl.pathname || "") + (Parsedurl.search || "")
( + ( parsedUrl.hash || "")
for (var key in headers) {
Data
});
.0.1");
console.log("The proxy server has been started on port 8088. ");
The implementation of the entire proxy server is relatively simple. First, create an HTTP server through the createServer() method in the http module, and then use the listen() method to make the HTTP server listen on a specific port. In createServer() ) method is the response method of the HTTP request. In fact, each HTTP request corresponds to a request event on the HTTP server. The HTTP server creation part in code listing 4 is actually equivalent to code listing 5. The implementation method given in
Listing 5. HTTP server creation method using event mechanism
var server = http.createServer();
server.on("request", function(req, res) {
}) ;
In the request processing method, the content of the proxy URL is obtained through the http.get() method. The event-based processing method is also used here. pres.on("data", function(chunk) {}). A processing method is added to the data event of pres. The function of this method is to write the obtained content back to the response of the original HTTP request. The same is true for the processing of the end event. Same thing. When developing with node.js, you will often encounter this scenario of using event processing methods and callback methods.
After introducing the event-driven mechanism of node.js, here are some commonly used modules.
Back to top
Common modules
node.js provides many modules related to network and file system operations by default. These modules are the basis for building server-side applications. Some of the common modules are explained in detail below.
Event Module
As mentioned earlier, node.js adopts an event-driven architecture, and many of its modules will generate various events. All event processing methods that can generate events can be added by module users. The objects are all instances of the EventEmitter class in the event module. The methods in the EventEmitter class are related to the generation and processing of events, as follows:
addListener(event, listener) and on(event, listener): These two methods are used to add event processing methods listener to a certain event event.
once(event, listener): This method adds a processing method listener that is executed only once for an event event. The processing method will be deleted after being executed once.
removeListener(event, listener): This method is used to delete the processing method listener on an event event.
emit(event, [arg1], [arg2], [...]): This method is used to generate an event event. The parameters after the event name event are passed to the corresponding event handling method.
Code Listing 6 gives an example of using the event module.
Listing 6. Usage example of event module
var events = require("events");
var emitter = new events.EventEmitter();
emitter.on("myEvent", function(msg) {
console. log(msg);
});
emitter.emit("myEvent", "Hello World.");
There is a special event error in the event module. EventEmitter generates this event when an error occurs. If there is no corresponding handling method for this event, the default behavior is to automatically terminate the program after outputting an error message. Therefore, be careful to always add a handler for the error event.
Streams
There are a variety of different data streams in node.js, including file systems, HTTP requests and responses, and TCP/UDP connections, etc. These streams are all instances of EventEmitter and can therefore produce a variety of different events. Streams can be divided into three types: read-only, write-only and read-write streams.
The readable stream mainly generates 4 events:
data: This event is generated when the data in the stream is read.
end: This event is generated when there is no data to read in the stream.
error: This event is generated when an error occurs while reading data.
close: This event is generated when the stream is closed.
In addition to the above events, there is also a pipe() method that can be used to connect the current readable stream to another writable stream. Data in the readable stream is automatically written to the writable stream.
The most commonly used methods in writable streams are write() and end(). The write() method is used to write data to the stream, and end() is used to end the writing operation.
In order to represent binary data, node.js uses the class Buffer to represent the data buffer to operate on binary data. The Buffer class stores data internally in the form of arrays. Once created, the size of the Buffer cannot be modified. Instances of the Buffer class are convertible to and from string types in JavaScript. You need to specify the encoding format when converting. The content from start to end in the Buffer can be converted into a string encoded by encoding through the toString(encoding, start, end) method of the Buffer class. Supported encoding formats are: ascii, utf8 and base64. A buffer can be initialized with a string str via new Buffer(str, encoding). write(string, offset, encoding) is used to write a string string in the encoding format encoding to the position starting at offset in the buffer.
Network operations
node.js provides some modules related to network operations, including TCP, UDP and HTTP, etc., which can implement network servers and clients.
The implementation related to the TCP protocol is in the net module. A TCP server can be created through the createServer(connectionListener) method of this module. The parameter connectionListener is the processing method when a client connects to the server, which is equivalent to the processing of the connect event. A TCP server is an instance of class Server. The listen method allows the server to listen on a specified port.
If you want to connect to an existing TCP server, you can use the createConnection(port, host) method to connect to the port port of the specified host host. The return value of this method is an instance of the Socket class, representing a socket connection. After obtaining an instance of the Socket class, you can write data to the connection through the write() method. If you want to get data from this connection, you can add a data event handling method.
Code Listing 7 shows a simple TCP server for expression calculation. You can connect to this server through the telnet command for testing.
Listing 7. Simple expression calculation server
var net = require("net");
var server = net.createServer(function(socket) {
socket.setEncoding("utf8");
var buffer = [ ], len = 0;
socket.on("data", function(data) { // Received client data
if (data.charCodeAt(0) == 13) {
var expr = buffer.join(" " ); socket.write("Wrong expression.");
expression. buffer.push(data);
server.listen(8180, "127.0.0.1");
console.log("The server has been started at port 8180.");
In addition to the TCP server, the modules http and https can implement HTTP and HTTPS servers respectively. dgram can implement UDP/Datagram socket connection, and the module tls can implement secure socket connection (SSL). These modules are all used similarly to the tcp module.
File system
The fs module in node.js is used to operate the local file system. The methods provided in the fs module can be used to perform basic file operations, including reading, writing, renaming, creating and deleting directories, and obtaining file metadata. Each method of operating files has both synchronous and asynchronous versions. The asynchronous version of the operation always uses a callback method as the last parameter. When the operation is completed, the callback method will be called. The first parameter of the callback method is always reserved for possible exceptions during operation. If the operation succeeds correctly, the value of the first parameter is null or undefined . The method name of the synchronous operation version is added with a Sync as a suffix after the corresponding asynchronous method. For example, the synchronous version of the asynchronous rename() method is renameSync(). Some common methods in the fs module are listed below, and only the asynchronous operation version is introduced.
rename(path1, path2): Rename the directory or file represented by path path1 to path path2.
truncate(fd, len): Truncate the length of the file corresponding to file descriptor fd to len.
chmod(path, mode): Modify the permissions of the directory or file represented by path to mode.
stat(path): Get the metadata of the directory or file represented by path. Metadata is represented using the Stats class.
open(path, flags, mode): Open a file represented by path path. The file descriptor can be obtained in the callback method.
read(fd, buffer, offset, length, position): Read the data of length bytes starting from the position position in the file represented by the given file descriptor fd, and store it in the buffer buffer starting from offset at the starting position. The actual number of bytes read can be obtained in the callback method.
readFile(filename, encoding): Read the contents of a file filename in the encoding format encoding. The content of the file can be obtained in the callback method.
writeFile(filename, data, encoding): Write the data data into the file filename in the encoding format encoding.In addition to the methods listed above for directly operating the file itself, you can also convert the file into a stream. createReadStream(path, options) and createWriteStream(path, options) are used to create readable and writable streams from files respectively. The parameter path represents the path to the file, and options is a JavaScript object representing options when reading or writing the file.
The implementation of a simple HTTP static file server is given in Code Listing 8.
Listing 8. HTTP static file server
var http = require("http"),
fs = require("fs"),
path = require("path"),
url = require("url");
var server = http.createServer(function(req, res) {
var pathname = url.parse(req.url).pathname;
var filepath = path.join("/tmp", "wwwroot", pathname) ;
var stream = fs.createReadStream(filepath, {flags : "r", encoding : null});
stream.on("error", function() {
res.writeHead(404);
res.end( );
});
stream.pipe(res);
});
server.on("error", function(error) {
console.log(error);
});
server.listen(8088 , "127.0.0.1");
As shown in code listing 8, first convert the path of the HTTP request into a file path on the server, then create a readable stream from the file, and finally use the pipe() method to convert the data stream of the file Passed into the response of the HTTP request.
Auxiliary modules
In addition to the common modules introduced above, node.js also provides some auxiliary modules.
The module path is used to handle paths on the file system. join() in this module is used to join multiple paths to form a complete path. For example, the result of join("/usr", "home", "test/index.html") is the path /usr/home/test/index.html. normalize() is used to normalize the path, remove redundant "/" and process ".." and ".". The resolve([from ...], to) method is used to obtain the absolute path of the given path to. If to is not an absolute path, add the previous parameters from right to left until an absolute path is obtained. If the absolute path cannot be obtained in the end, add the current working directory. Assuming that the current working directory is /usr/home, then the return result of resolve("test", "index.html") is /usr/home/test/index.html. The dirname() method is used to get the directory part of the path. For example, the return result of dirname("/usr/home/index.html") is /usr/home. basename() is used to get the last part of the path. For example, the return result of basename("/usr/home/index.html") is index.html. extname() is used to get the file extension part of a path. For example, the return result of extname("/usr/home/index.html") is .html.
The module url is used to parse URLs. The parse(urlStr, parseQueryString) method is used to parse a URL string urlStr into several parts such as host name, port and path. The return value of this method is a JavaScript object containing properties such as protocol, hostname, port, pathname and query. If the value of the parameter parseQueryString is true, the query string part contained in the URL will also be parsed. The format(urlObj) method is the opposite of the parse() method and is used to construct a URL string from a JavaScript object.
The module querystring is used to process query strings in URLs. The stringify(obj) method is used to convert a JavaScript object obj into query string format. For example, stringify({a : 1, b : "good"}) returns a=1&b=good. parse(str) is used to parse a query string into a JavaScript object.
The module vm can be used to execute JavaScript code. The method runInThisContext(code) is used to execute a piece of JavaScript code code and return its results. JavaScript code run through this method cannot access the scope of the current code. The runInNewContext(code, [sandbox]) method is also used to execute JavaScript code. Unlike runInThisContext(), the JavaScript code run through this method uses the sandbox object as the global object. For example, the return result of runInNewContext("a + 3", {a : 4}) is 7. The createScript(code) method is used to pre-compile a JavaScript code, but does not execute it immediately. The return value of this method is a Script object. This object also has two methods, runInThisContext() and runInNewContext([sandbox]), which have similar meanings to the two methods mentioned above.
The module os provides some information related to the underlying operating system. Including hostname() is used to obtain the host name of the operating system; type() is used to obtain the type of operating system; release() is used to obtain the release version number of the operating system; uptime() is used to obtain the system running time in seconds. ;cpus() is used to obtain CPU related information. freemem() and totalmem() are used to obtain the total memory and available memory of the system respectively.
The module util provides some commonly used auxiliary methods. The debug(string) method is used to output information to the standard error stream. The log(string) method is used to output information with timestamps to the standard output stream. The inspect(object, showHidden, depth) method is used to output the internal structure of an object. The parameter object is the object to be inspected. showHidden indicates whether to view the hidden attributes of the object. depth indicates the depth of the object hierarchy being viewed. The default value is 2. The inherits(constructor, superConstructor) method is used to implement the prototype-based inheritance mechanism in JavaScript.
After introducing the common modules provided by node.js, here is a complete example to illustrate the usage of node.js.
Back to top
Instance analysis
The function implemented by this example is to dynamically monitor the memory usage status of the server, that is, the memory occupancy rate. Obtaining the memory usage on the server is relatively simple. You only need to use the method provided by the os module, namely freemem()/totalmem(). In order to monitor memory occupancy in real time, the server needs to transmit data to the browser in real time. The best implementation here is the WebSocket specification introduced in HTML 5. This specification is supported in new browsers such as Firefox 4 and Google Chrome. At the same time, the server side also needs to support this specification. Socket.IO provides support for the WebSocket specification on node.js, including server-side and browser-side code. Code Listing 9 shows the server-side code using Socket.IO.
Listing 9. Server-side code for monitoring memory usage
var io = require('./socket.io');
var io = io.listen(server);
io.on("connection", function (client){
setInterval(function() {
client.send(os.freemem() / os.totalmem());
}, 500);
});
In code listing 9, server is node An HTTP server object in .js, used to respond to general HTTP requests. Socket.IO can intercept requests from the node.js HTTP server and hand over some requests to Socket.IO for processing. The processing logic here is that when a client connects, the server's memory usage is sent to the client every 500 milliseconds. Code Listing 10 shows the browser-side HTML and JavaScript code.
Listing 10. Browser-side code to monitor memory usage