Node.js提供了系统模块http用来写服务端代码。http
模块是系统核心模块,不需要下载只需要直接引入即可,引入代码如下:
var htttp = require('http');
创建简单的服务端代码如下:
// 第一步,引入http模块 var http = require("http"); // 第二步,通过http模块的createServer方法创建一个server var server = http.createServer(); // 第三步,监听"request"事件,获取请求,发送响应 server.on("request", function (request, response) { console.log("接收到客服端请求,请求路径是:" + request.url); }); // 第四步,设置监听端口,启动服务器 server.listen(8888, function () { console.log("服务器启动成功,可以通过 http://127.0.0.1:8888 来进行访问"); });
注意:
request
是请求处理函数,会接收两个参数:Request请求对象和Response响应对象。请求对象Request可以获取客服端的一些请求信息,如请求路径;响应对象Response可以来给客服端发送响应消息。listen()
函数中的端口号可以任意设置。在浏览器输入http://localhost:8888/hello
访问就可以在控制台看到打印结果:
如果我们要响应一些数据给客服端,可以通过响应对象Response来输出一些信息。
response.write(data);
:向客户端输出数据,可以多次调用该方法。response.end();
:结束输出。// 第一步,引入http模块 var http = require("http"); // 第二步,通过http模块的createServer方法创建一个server var server = http.createServer(); // 第三步,监听"request"事件,获取请求,发送响应 server.on("request", function (request, response) { console.log("接收到客服端请求,请求路径是:" + request.url); // 通过Response对象的write()方法可以向客户端输出数据 response.write("hello world!\n"); response.write("hello node.js!"); // 最后还要通过end()方法来结束输出,否则会一直等待 response.end(); }); // 第四步,设置监听端口,启动服务器 server.listen(8888, function () { console.log("服务器启动成功,可以通过 http://127.0.0.1:8888 来进行访问"); });
在浏览器查看如下:
其实可以直接通过response.end(data);
方法向客户端输出数据。
server.on("request", function (request, response) { console.log("接收到客服端请求,请求路径是:" + request.url); // 通过Response对象的end(data)方法可以向客户端输出数据 response.end("hello world!") });
现在写的代码功能非常小,无论什么请求,响应的结果都是"hello world!"
,我们需要根据不同的请求路径来响应不同的结果,如/index
响应首页、/login
响应登录、/register
响应注册等。可以通过if判断url来进行不同的响应:
// 第一步,引入http模块 var http = require("http"); // 第二步,通过http模块的createServer方法创建一个server var server = http.createServer(); // 第三步,监听"request"事件,获取请求,发送响应 /* 根据不同的请求路径发送不同的响应结果: 1.获取请求路径 通过request.url属性获取到请求的路径,即端口之后的那一部分路径,如"http://127.0.0.1:8888"默认是"/","http://127.0.0.1:8888/hello"是"/hello" 2.根据if判断来根据路径进行不同的响应返回 */ server.on("request", function (request, response) { // 获取url属性,然后if判断 var url = request.url; if (url === '/') { response.end('index page'); } else if (url === '/hello') { response.end('hello world!'); } else if (url === '/login') { response.end('login page'); } else { response.end('404 Not Found.'); } }); // 第四步,设置监听端口,启动服务器 server.listen(8888, function () { console.log("服务器启动成功,可以通过 http://127.0.0.1:8888 来进行访问"); });
当我们直接响应返回中文数据的时候,浏览器页面会中文乱码显示。因为node.js响应回的数据编码格式是UTF-8,而中文系统的浏览器页面解析用的是GBK。通过设置Content-Type响应头来设置响应数据内容的编码格式:
// 第一步,引入http模块 var http = require("http"); // 第二步,通过http模块的createServer方法创建一个server var server = http.createServer(); // 第三步,监听"request"事件,获取请求,发送响应 server.on("request", function (request, response) { // 通过响应对象Response的setHeader(name, value)方法设置响应头,来设定响应数据内容是什么类型的 response.setHeader('Content-Type', 'text/plain; charset=utf-8'); response.end('hello 世界!'); }); // 第四步,设置监听端口,启动服务器 server.listen(8888, function () { console.log("服务器启动成功,可以通过 http://127.0.0.1:8888 来进行访问"); });
Content-Type
还可以设置其他响应内容格式:
// 第一步,引入http模块 var http = require("http"); var fs = require("fs"); // 第二步,通过http模块的createServer方法创建一个server var server = http.createServer(); // 第三步,监听"request"事件,获取请求,发送响应 server.on("request", function (request, response) { // 通过响应对象Response的setHeader(name, value)方法设置响应头,来设定响应数据内容是什么类型的 var url = request.url; if (url === '/plain') {// 普通文本 // text/plain是普通文本类型 response.setHeader('Content-Type', 'text/plain; charset=utf-8'); response.end('hello 世界!'); } else if (url === '/html') {// 直接输出html内容 // text/html是html格式的字符串 response.setHeader('Content-Type', 'text/html; charset=utf-8'); response.end("<p>hello node.js.<a href='#'>点我</a></p>") } else if (url === '/html-from-file') {// 从.html文件中读取内容 fs.readFile('hello.html', function (err, data) { if (!err) { // text/html是html格式的字符串,通常读取.html文件中的内容 response.setHeader('Content-Type', "text/html; charset=utf-8"); response.end(data); } else { response.setHeader('Content-Type', 'text/plain; charset=utf-8'); response.end('hello.html文件读取失败,请稍后重试!'); } }); } else if (url === '/star.jpg') { fs.readFile('star.jpg', function (err, data) {// 图片 if (!err) { // data默认是二进制数据,可以通过data.toString()方法转换成能识别的字符串 // response.end(chunk)方法中的参数支持两种数据类型:二进制和字符串 // 图片就不需要指定编码了,通常只有字符串才需要指定编码 response.setHeader('Content-Type', "image/jpeg"); response.end(data); } else { response.setHeader('Content-Type', 'text/plain; charset=utf-8'); response.end('hello.html文件读取失败,请稍后重试!'); } }); } }); // 第四步,设置监听端口,启动服务器 server.listen(8888, function () { console.log("服务器启动成功,可以通过 http://127.0.0.1:8888 来进行访问"); });
关于JavaScript代码规范,比较规范的有:
而关于JavaScript中分号是否添加的问题:
;`hello`.toString() /* 当采用了无分号的代码风格时,只需要注意下面几个情况: 当一行代码是以(或[或`开头的时候,则在前面补一个分号就能避免一些语法解析错误。 所以会发现一些第三方代码中以;开头。 所以无论代码是否有分号,都建议如果一行代码是以(或[或`开头的,则最好都在前面补上 一个分号,有些人也会使用!或~符号,都是同样的效果。 */
可以用来服务端渲染数据,然后直接传到客户端使用。
官方教程:art-template
推荐博客:模板引擎 – art-template
我们可以通过响应对象Response的write或end方法向客户端输出文本数据,也可以输出html格式的字符串,更可以读取.html
文件向客户端输出数据。如果我们向客户端输出大量数据,那么可能需要拼接html格式的字符串来显示不同的内容,例如:
// 导入http模块 var http = require("http"); // 创建服务器 var server = http.createServer(); // 监听request事件 server.on("request", function (request, response) { var name = "张三"; var age = 18; var hobbies = ["听歌", "阅读", "打游戏"]; // 拼接字符串 var html = ` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Hello</title> </head> <body> <p>大家好,我的名字是:` + name + `</p> <p>我今年` + age + `岁了</p> <p>我的爱好有:` + hobbies[0] + `;` + hobbies[1] + `;` + hobbies[2] + `</p> </body> </html> `; response.end(html); }); // 服务器监听端口,启动服务器 server.listen(8888, function () { console.log("服务器启动了!可以通过 http://127.0.0.1:8888 进行访问!") });
但我们发现拼接字符串非常麻烦,如果html代码很多,那么就更麻烦了。所以可以使用模板引擎,在html文件中的一些关键信息用一些符号进行标记,然后统一进行替换。而art-template就是这样的一个模板引擎。使用的步骤如下:
// 第一步,安装art-template模块 npm install art-template // 第二步,在js文件中引入该模块 var template = require('art-template'); // 第三步,使用参考API template.render('模板字符串', 替换对象);
所以我们先在当前项目下安装art-template
模块。
然后创建一个html文件,在里面使用模板引擎的{{ }}
语法。其中{{ }}
里面的变量名任意取,但遍历需要遵循模板引擎的语法格式。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Index</title> </head> <body> <p>大家好,我的名字是:{{ name }}</p> <p>我今年{{ age }}岁了</p> <p>我的爱好有:{{ each hobbies}} {{ $value }} {{ /each }}</p> </body> </html>
然后在js里通过读取.html
文件的方式向客户端输出数据,并使用模板引擎渲染数据。
// 导入http模块 var http = require("http"); // 导入art-template模块 var template = require("art-template"); // 导入fs模块 var fs = require("fs"); // 创建服务器 var server = http.createServer(); // 监听request事件 server.on("request", function (request, response) { fs.readFile("./index.html", function (err, data) { if (!err) { // 默认读取到的data是二进制数据,模板引擎的render方法需要接收的是字符串,所以要将data转换成字符串才能给模板引擎使用 var ret = template.render(data.toString(), { name: '张三', age: 19, hobbies: ['阅读', '听歌', '打游戏'] }); response.end(ret); } }); }); // 服务器监听端口,启动服务器 server.listen(8888, function () { console.log("服务器启动了!可以通过 http://127.0.0.1:8888 进行访问!") });
注意:
template.render()
方法在这里传入了两个参数,第一个参数就是写了模板语法的html字符串,不过这里传入的是从文件读取的html字符串;第二个参数是数据对象,里面定义了传给模板引擎的值,其中键名就是{{ }}
里面的变量名,键值就是要赋予的具体值。template.render()
方法的返回值就是渲染数据成功返回的字符串,可以直接输出到客户端了。在浏览器访问结果如下:
需求:通过http
模块和art-template
模板引擎来实现文件浏览器的效果,如下图:
涉及模块:
其他技术:
实现原理:根据请求的路径使用fs模块直接读取系统中的文件目录及其相关属性,然后使用模板引擎art-template渲染出来。
在Windows系统下的代码和Linux系统下的路径有所区别:
app.js
var http = require('http'); var template = require('art-template'); var fs = require('fs'); var path = require('path'); var server = http.createServer(); server.on('request', function (request, response) { var url = request.url; if (url === '/') { fs.readFile('./index.html', function (err, data) { if (!err) { var files = [ {isDir: true, name: "C:/", path: "C:/"}, {isDir: true, name: "D:/", path: "D:/"}, {isDir: true, name: "E:/", path: "E:/"}, {isDir: true, name: "F:/", path: "F:/"}, {isDir: true, name: "G:/", path: "G:/"}]; var ret = template.render(data.toString(), {files: files}); response.end(ret); } }); } else if (url.startsWith('/C:') || url.startsWith('/D:') || url.startsWith('/E:') || url.startsWith('/F:') || url.startsWith('/G:')) { url = decodeURIComponent(url.substring(1));// 中文是进行URL编码的,所以需要对路径进行解码 // 根据目录路径读取该目录下的所有文件名称 fs.readdir(url, function (err, files) { fs.readFile('./index.html', function (err, data) { if (!err) { var paths = []; for (var i in files) { var item = new Object(); // 文件名 item.name = files[i]; // 文件绝对路径 var p = path.join(url, files[i]); item.path = p; if (files[i].startsWith('System Volume Information')) { item.isDir = true; } else if (files[i].startsWith('pagefile.sys') || files[i].startsWith('swapfile.sys')) { item.isDir = false; } else { // 是否是目录 var stats = fs.statSync(p); item.isDir = stats.isDirectory(); // 文件大小 item.size = stats.size; // 文件的最后一次修改时间 var date = new Date(stats.mtime); var mtime = date.getFullYear() + "/" + date.getMonth() + "/" + date.getDay() + "\t\t" + date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds(); item.modifyTime = mtime; // 将对象添加到数组中 paths.push(item); } } var ret = template.render(data.toString(), { title: url, parentDir: path.resolve(url, '..'),// 获取当前目录的上一级目录 files: paths }); response.end(ret); } }) }); } else { console.log(url); } }); server.listen(8888, function () { console.log('server running...') });
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>文件浏览器</title> </head> <body> <h4>file:///{{title}} 的索引</h4> <p><a href="/{{parentDir}}">回到上一层文件夹</a></p> <table> <tr> <th>名称</th> <th>大小</th> <th>修改时间</th> </tr> {{each files}} <tr> <td width="300px"> {{if $value.isDir}} <a href="/{{$value.path}}">{{$value.name}}</a> {{else}} <span>{{$value.name}}</span> {{/if}} </td> <td width="100px"> {{$value.size}} </td> <td width="200px"> {{$value.modifyTime}} </td> </tr> {{/each}} </table> </body> </html>
效果图如下:
如果要把代码部署到服务器上,使用下面的代码,因为Windows系统有盘符,而Linux系统的根目录是/
。
app.js
:核心代码没什么区别,只是根路径是/
而已,不需要针对盘符进行处理。
var http = require('http'); var template = require('art-template'); var fs = require('fs'); var path = require('path'); var server = http.createServer(); server.on('request', function (request, response) { var url = request.url; console.log(url); if (url.startsWith('/')) { url = decodeURIComponent(url);// 中文是进行URL编码的,所以需要对路径进行解码 console.log(url); // 根据目录路径读取该目录下的所有文件名称 fs.readdir(url, function (err, files) { fs.readFile('./index.html', function (err, data) { if (!err) { var paths = []; for (var i in files) { var item = new Object(); // 文件名 item.name = files[i]; // 文件绝对路径 var p = path.join(url, files[i]); item.path = p; if (files[i].startsWith('System Volume Information')) { item.isDir = true; } else if (files[i].startsWith('pagefile.sys') || files[i].startsWith('swapfile.sys')) { item.isDir = false; } else { // 是否是目录 var stats = fs.statSync(p); item.isDir = stats.isDirectory(); // 文件大小 item.size = stats.size; // 文件的最后一次修改时间 var date = new Date(stats.mtime); var mtime = date.getFullYear() + "/" + date.getMonth() + "/" + date.getDay() + "\t\t" + date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds(); item.modifyTime = mtime; // 将对象添加到数组中 paths.push(item); } } var ret = template.render(data.toString(), { title: url, parentDir: path.resolve(url, '..'),// 获取当前目录的上一级目录 files: paths }); response.end(ret); } }) }); } else { console.log(url); } }); server.listen(8888, function () { console.log('server running...') });
index.html
:只是修改了<a>
标签的href
属性,没有了前面的斜杠。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>文件浏览器</title> </head> <body> <h4>file:///{{title}} 的索引</h4> <p><a href="{{parentDir}}">回到上一层文件夹</a></p> <table> <tr> <th>名称</th> <th>大小</th> <th>修改时间</th> </tr> {{each files}} <tr> <td width="300px"> {{if $value.isDir}} <a href="{{$value.path}}">{{$value.name}}</a> {{else}} <span>{{$value.name}}</span> {{/if}} </td> <td width="100px"> {{$value.size}} </td> <td width="200px"> {{$value.modifyTime}} </td> </tr> {{/each}} </table> </body> </html>
效果如下: