~ 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....'); });

