使用node.js搭建原生服务器,实际是使用node中自带的http模块创建一个服务,这个服务自运行起对特定接口进行监听。
当有对特定接口的请求时,会执行指定的回调函数,在回调函数中对请求的url进行解析,根据不同的url,执行并返回不同的结果。
我们先建立一个js文件(这里确保已安装了node.js环境),在js文件中引入http模块(无需install直接引入即可),然后建立一个接口监听器。
// index.js
const http = require("http")
http.createServer(function(req, res) {
res.writeHead(200)
res.end(req.url)
})
.listen(3001)
在文件所在的目录打开命令行,输入node index执行该文件,执行后命令行广播停在下一行开始的位置,代表服务器已经成功启动了。
打开浏览器,可以在localhost:3001访问到该接口,返回的值直接打印在了页面上。
结合上面的代码我们可以猜到,res.writeHead函数是为接口返回赋状态码,res.end决定接口给出的值,req.url是对请求url的获取。
在上面的示例中,服务器对任意请求的返回值是相同的,就是自身的url,接下来我们需要对url进行分析,以给出更多路由对应的解决方案。
我们可以使用node原生url模块对url进行解析,它会根据url解析出一个对象,内部包括该url的路由地址和参数。
再使用原生模块querystring将参数解析成对象。
首先我们引入http、url、querystring模块。
const http = require("http")
const url = require('url')
const querystring = require('querystring')
在createServer中我们使用url模块将原始url字符串解析成对象。
const url2 = url.parse(req.url)
这时我们可以通过url2.pathname来获取路由,通过url2.query来获取参数。
最后通过querystring对参数解析,返回结果。
const http = require("http")
const url = require('url')
const querystring = require('querystring')
http.createServer(function(req, res) {
const url2 = url.parse(req.url)
if(url2.pathname == '/') {
const query = querystring.parse(url2.query)
const param = query.action
res.writeHead(200)
res.end(param)
}
})
.listen(3001)
通过对url的解析,现在我们可以对不同的路由执行不同的逻辑代码,并且可以获取到传入的参数了。
http.createServer(function(req, res) {
const url2 = url.parse(req.url)
if(url2.pathname == 'favicon.ico') {
res.writeHead(200)
res.end('hello')
return
}
if(url2.pathname == '/') {
const query = querystring.parse(url2.query)
const param = query.action
res.writeHead(200)
res.end(param)
}
if(url2.pathname == '/game') {
res.writeHead(200)
res.end('hellow game')
}
})
.listen(3001)
当前我们返回的是数据,返回html文件该如何操作呢。
首先我们引入node模块fs,
使用fs创建管道使对应地址的文件注入res即可,不理解没关系,看代码。
...
const fs = require('fs')
http.createServer(function(req, res) {
const url2 = url.parse(req.url)
if(url2.pathname == '/') {
fs.createReadStream(__dirname+'/index.html').pipe(res)
}
})
.listen(3001)
__dirname是node自带的一个变量,值为当前目录路径。
这样,当访问根路径时,html文件将出现在页面上。
使用node.js搭建原生服务器基础部分就讲完了,不过现在已没人使用它来搭建服务器啦,下面介绍nodejs搭建服务器的热门框架,express和koa。
在原生搭建中,我们需要使用url模块对url进行分析,使用if判断不同的路由,以执行不同的代码,当路由变多时,代码冗杂在一起。
express框架对路由进行了封装,它自动判断当前请求的路由将其分发到对应的回调函数中,这就是express框架特有的路由系统。
我们首先引入express模块(这是需要使用npm install express安装的),并创建express对象。
const express = require('express')
const app = express()
express是一个工厂函数,返回一个可使用的对象,该对象可以挂载路由处理逻辑,和开启接口监听。
...
app.get('/favicon.ico', function(req, res) {
res.status(200)
res.send('hello')
return
})
app.listen(3001)
在express中,我们不再需要url模块和querystring模块,框架为我们完成了封装。
我们可以使用req.query来获取参数。
...
app.get('/', function(req, res) {
const query = req.query
res.param = query.action
res.status(200)
res.send(res.param)
})
...
但是在返回html文件时,我们仍需要引入fs模块,只是使用格式有所变化。
const fs = require('fs')
...
app.get('/', function(req, res) {
res.send(fs.readFileSync(__dirname+'/index.html', 'utf-8'))
})
...
在上面的代码中看到,在设置返回内容时,有也一些语法变化,更加简洁了。
express框架主要做的事情就是这些:
现在来讲一下第四条,这可以说是最重要的一条了。
express提供了中间件开发模式,而koa完善了该模式。
简单的说,就是注册多个回调函数,当路由被触发时,沿着顺序执行多个回调函数,回调函数中接受next参数。
当调用next()时,该回调函数暂停,进行下一个回调函数,当回调函数队列执行完毕,继续执行前面暂停的函数,直到回到第一个函数。
下面这段代码的执行结果是 1 2 3 发出响应。
app.get('/game', function(req, res, next) {
const query = req.query
res.param = query.action
console.log(1)
next()
console.log(3)
res.status(200)
res.send(game(res.param))
}, function(req, res) {
console.log(2)
})
但是,如果在后面的函数中执行异步操作,next不会等待异步完成,而是在同步代码执行完后就继续执行next后的代码。
这是express的关键缺点之一,也是很多人选择koa的原因。
koa是一个轻量级的框架,轻到它没有自己的路由系统,但是它提供了路由系统的中间件,只要引入就可以使用,使用方法和express差不多,只是语法有变化。
首先我们引入koa(koa需要使用npm install安装),获得koa实例。
在获得实例这里koa和express有所不同,koa使用new创建。
const koa = require('koa')
const app = new koa()
引入路由插件(中间件)koa-mount,封装路由逻辑,启动接口监听。
另外,在路由中不用send了,给ctx.body赋值即可。
const fs = require('fs')
const koa = require('koa')
const mount = require('koa-mount')
const app = new koa()
app.use(
mount('/favicon.ico', function(ctx){
ctx.status = 200
})
)
app.use(
mount('/', function(ctx){
ctx.status = 200
ctx.body = fs.readFileSync(__dirname+'/index.html', 'utf-8')
})
)
app.listen(3001)
koa对异步进行了处理,中间件的回调函数是可以加async前缀的,在next前加await前缀,next会等待异步完成后再执行后面的内容。
下面是一个koa中间件语法的示范。
猜猜看下面代码的执行结果是什么?
const gamekoa = new koa()
gamekoa.use(
async function(ctx, next){
console.log(11)
await next()
console.log(12)
})
gamekoa.use(
async function(ctx, next){
return new Promise((res) => {
console.log(21)
ctx.status = 200;
ctx.body = '122333'
setTimeout(() => {
console.log(22)
res()
})
})
}
)
app.use(
mount('/game', gamekoa)
)
上面代码执行结果为11 21 22 12,值得强调的是,即使在输出21后返回了数据,也不会中断中间件代码的执行,在数据返回后剩下的代码仍然执行了。