~ Node.js 内置模块 – 服务器功能的实现

Node.js 开发的核心是使用 JavaScript 来编写 web 服务器端程序。

理解 web 服务器端程序的工作原理,首先需要对 HTTP 协议有一定的了解,HTTP 协议,不是基于某种特定语言的,而是一种通用的应用层协议,不同的语言有不同的实现细节,但整体来说,万变不离其宗,思想是相同的。

Node.js 作为一种宿主运行环境,以 JavaScript 为宿主语言,有着一套自己的实现方式:

  • 静态服务器功能

( 后端 )路由处理

  • 动态网站功能
    • 数据交互 — 参数的传递和处理
    • 模板引擎

== Express( 框架 ),对上述功能进行了一定的封装,并形成了一套新的 API。


就服务器( 容器 )来讲,传统的 http 服务器,一般由 Apache、Nginx 或者 IIS 之类的软件来担任,

但,Node.js 并不需要,它提供了 http 模块,使用自身内置的 API,就可以构建服务器。


http( 静态服务器功能 )

Node.js 中,http 模块封装了一个 http 服务器、一个简易的 http 客户端

// 引入模块
   let http = require('http');

[1] 常规写法

http.Server 是一个基于事件的 http 服务器

  • http.createServer([options][, requestListener])  返回新建的 http.Server 实例
  • server.listen(options[,callback])  启动 HTTP 服务器的监听连接
let server = http.createServer();  // 创建一个server(服务器)实例
// 绑定请求事件
server.on('request',(req,res)=>{
	// 使用客户端工具
	res.write('hello');
	res.end('world');
});
// 设置监听端口
server.listen(3000,()=>{
	console.log('localhost:3000,启动成功');
});

[2] 简易写法

http.request 是一个客户端工具,用于向 http 服务器发送请求

其中,参数:req 和 res,分别代表了请求对象和响应对象

  • req.url获取 URL 中的路径(端口之后的部分)

// 同一 URL 下,不同路径访问的资源是不一样的,如何实现呢? — 路径分发:req.url.startsWith()

  • res.write()向请求的客户端发送响应内容,在 res.end() 之前,可以被执行多次
  • res.end()结束响应,告诉客户端所有消息已经发送。

当所有要返回的内容发送完毕时,该函数必须被调用一次。如何不调用该函数,客户

// 基本语法
http.createServer(
   // 一个简易的客户端工具
   (res,req)=>{}
).listen()
// 代码示例:使用分支控制进行路径的分发和页面响应

var http = require('http');
const path = require('path');
const fs = require('fs');
 
// 读取文件(页面)内容
let readFile = (url,response)=>{
	fs.readFile(path.join(__dirname,'view',url),'utf8',(err,fileContent)=>{
		if(err){
			response.end('server error');
		}else{
			response.end(fileContent);
		}
	});
}
 
http.createServer((request,response)=>{
	// 处理路径分发
	if(request.url.startsWith('/index')){
		readFile('index.html',response);
	}else if(request.url.startsWith('/about')){
		readFile('about.html',response);
	}else if(request.url.startsWith('/list')){
		readFile('list.html',response);
	}else{
		// 设置响应头(响应类型和编码)
		response.writeHeader(200,{
			'content-Type':'text/plain;charset=utf8'
		});
		response.end('页面找不到了');
	}
}).listen(3000,'127.0.0.1',()=>{
	console.log('Server is running');
});

[3] 进阶:封装实现

  • 准备工作
    • JSON文件( 文件类型的匹配 )— mime.json
    • 相关静态页面(index、about、article)
  • 封装好的静态资源服务功能,如下
// ss.js(封装好的静态资源服务功能)
const path = require('path');
const fs = require('fs');
const mime = require('./mime.json');
 
exports.staticServer = (request,response,root)=>{
	fs.readFile(path.join(root,request.url),(err,fileContent)=>{
		if(err){
			// 没有找到文件
			response.writeHead(404,{
				'Content-Type': 'text/plain;charset=utf8'
			});
			response.end('页面找不到了');
		}else{
			// 通过 mime.json 文件匹配文件类型
			let dtype = 'text/html';
			// 获取请求文件的后缀
			let ext = path.extname('request.url');
			// 如果请求的文件后缀合理,就获取标准的响应格式
			if(mime[ext]){
				dtype = mime[ext];
			}
			// 如果响应的内容是文本,就设置成utf8编码
			if(dtype.startsWith('text')){
				dtype += ';charset=utf8'
			}
			// 设置响应头
			response.writeHead(200,{
				'Content-Type':dtype
			})
			response.end(fileContent);
		}
	});
}
  • 应用 — 入口文件
// 服务器文件( 入口文件 ) app.js
 
const http = require('http');
// 导入自己封装好的静态资源服务功能
const ss = require('./ss.js');  
const path = require('path');
 
http.createServer((request,response)=>{
	ss.staticServer(request,response,path.join(__dirname,'view'));   // 根路径
	// 封装完成后,也可以灵活的设置不同的静态资源的根路径,类似:
	// ss.staticServer(request,response,path.join('C:\\Users\\view\\Destop’,'test'));
}).listen(3000,()=>{
	console.log('Server is running');
});

url  /  querystring( 动态网站功能 )

提到动态网站功能,通常会涉及到参数的请求处理:

  • url 模块  —  get 请求,简单来说就是,从 url 地址中获取参数
  • querystring 模块  —  post 请求,关键在于:通过 post 请求,把数据传输到后台,再使用该模块转换格式

[1] url 模块

// 引入模块
   let url = require('url');
  • url.parse( 把一个 url 转换成对象;如果第二个参数为 true,则 query 会被设置为一个对象,方便调用。
// url地址
let urlstr = 'http://www.baidu.com/abc?flag=123&keyword=java';
// 把 url 转换为一个对象
let urlObj = url.parse(urlstr,true);   // 结果如下:

/*
	Url {
	  protocol: 'http:',
	  slashes: true,
	  auth: null,
	  host: 'www.baidu.com',
	  port: null,
	  hostname: 'www.baidu.com',
	  hash: null,
	  search: '?flag=123&keyword=java',
	  query: [Object: null prototype] { flag: '123', keyword: 'java' },
	  pathname: '/abc',
	  path: '/abc?flag=123&keyword=java',
	  href: 'http://www.baidu.com/abc?flag=123&keyword=java'
	}
*/
  • url.format()   把一个对象格式化为 url 形式的字符串
console.log(url.format(urlobj));  // http://www.baidu.com/abc?flag=123&keyword=java

// 代码示例:通过 url 模块解析、获取 get 请求的参数

const http = require('http');
const path = require('path');
const url = require('url');
 
http.createServer((request,response)=>{
	let obj = url.parse(request.url,true); // 第二个参数为true,query被设置成为一个对象
	// 获取get请求参数
	response.end(obj.query.username + '=========' + obj.query.password);
}).listen(3000,()=>{
	console.log('running');
})
 
// 在浏览器中访问:http://localhost:3000/?username=zhangsan&password=123456

[2] querystring 模块

// 引入模块
   const querystring = require('querystring');

  • querystring.parse   用于把 “foo=bar&abc=xyz” 这种形式的字符串转换成对象
let param = 'username=lisi&password=123'
let obj = querystring.parse(param);
console.log(obj.username);  // lisi
  • querystring.stringify  该方法作用相反,把对象转换成字符串( 符合参数的基本格式 )
let obj1 = {
	flag:'123',
	abc:['hello','hi']       // 参数名称如果相同,会转化为一个数组来存储不同的值
}
let str1 = querystring.stringify(obj1);
console.log(str1);  // flag=123&abc=hello&abc=hi

// 代码示例:基于事件的方式,获取 post 请求参数,并处理

let http = require('http');
 
http.createServer((req,res)=>{
	if(req.url.startsWith('/login')){
		
		// 需要通过基于事件的方式进行处理
		let pdata = '';
		// 数据传输
		req.on('data',(chunk)=>{
			postdata += chunk;
		});
		// 传输结束
		req.on('end',()=>{
			// postdata 即post请求数据
			let postobj = querystring.parse(postdata);  // 将post请求数据转换为一个对象
			
			// 返回响应数据
			res.end(obj.username + '----' + obj.password);
		});
	}
}).listen(3000,'127.0.0.1',()=>{
	console.log('server is running');
})
 
// 然后,可以使用 postman 插件或者直接使用表单进行 post 请求的测试

综合案例:登录验证功能的实现

  • 前端
// view/login.html
 
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
	</head>
	<body>
		<form action="http://localhost:3000/login" method="post">
			用户名:<input type="text" name="username">
			密码:<input type="password" name="password">
			<input type="submit" value="提交">
		</form>
	</body>
</html>

  • 服务器端
# 入口文件
 
/*
    登录验证功能
*/
const http = require('http');
const url = require('url');
const querystring = require('querystring');
const ss = require('./ss.js');
 
http.createServer((request,response)=>{
    // 启动静态资源服务
    if(request.url.startsWith('/view')){
        ss.staticServer(request,response,__dirname);
    }
    console.log(request.url);
    // 动态资源
    if(request.url.startsWith('/login')){
		// 不同的请求方式 + 路径分发功能,构成了路由策略(后续)
		
        // get请求
        if(request.method == 'GET'){
            let param = url.parse(request.url,true).query;
            if(param.username == 'admin' && param.password == '123'){
                response.end('get success');
            }else{
                response.end('get failure');
            }
        }
        // post请求
        if(request.method == 'POST'){
            let pdata = '';
            request.on('data',(chunk)=>{
                pdata += chunk;
            });
            request.on('end',()=>{
                let obj = querystring.parse(pdata);
				// console.log(obj.username + '用户名');
				// console.log(obj.password + '密码');
                if(obj.username == 'admin' && obj.password == '123'){
                    response.end('post success');
                }else{
                    response.end('post failure');
                }
            });
        }
    }
 
}).listen(3000,()=>{
    console.log('running....');
});