HTTP开发之Connect工具集——中间件
继学习node.js的TCP API和HTTP API之后,node.js web开发进入了正轨,但这就好像Java的servlet一样,我们不可能使用最基础得Servlet对象去写网站,我们也不能使用最基本的node http API去写一个完整得网站,我们需要更加强大得工具集,web套件,甚至是web开发框架(诸如Java下的Spring MVC),来提供开发者更人性化得web开发环境。
创建网站的基本任务——为何要有中间件
- 独立托管静态文件,诸如:html,css,js,images
- 处理错误和不存在的地址
- 处理不同类型的请求
如果我们上来就直接用上一章的http模块来做的话,我们需要先实现一个“框架”才能做到一个网站最基本的任务,如果是运用生产环境的话,我们希望有这样的一个“框架”——connect工具集,最好称它为工具集,他离所谓的“框架”依旧有点差距。
Connect是基于http API之上的,也就是基于node http模块写出来的,方便web开发者使用,他提供了一些工具方法能够上一些重复性的工作便于实现,让开发者更加专注于应用的功能业务。
Connect已经是十分基础的node web开发工具集,在实际开发中很少看到,以后回介绍稍微高级点的express(对于实际开发来说依然很基础)
特别注意:《了不起的node.js》一书中的代码和connect版本过时了,如果使用最新版的connect需要参照如下方法使用!
导入Connect等中间件模块
Connect模块并非node.js的原生模块,需要引入外部模块
同时还要导入“serve-static”,“content-disposition”提供静态文件访问支持
最终完整package.json:
{ ???"name":"node-connect", ???"version":"0.0.1", ???"description":"Use connect to create a website", ???"dependencies":{ ???????"connect":"latest", ???????"serve-static":"latest", ???????"content-disposition":"latest" ???}}
node npm包搜索管理网站:https://www.npmjs.com/
注:除了connect外,还导入了serve-static,这个包原本属于connect集合,后官方独立出来了,《了不起的node.js》使用的是1.8.x版本的connect(太老了)还包含着static中间件,这里使用了3.0.0+的版本,则需要额外导入这个静态中间件模块。
这些是官网独立的中间件(Connect/Express负责管理)
将不同功能的中间件从connect独立出来是一件好事,方便管理维护!(对于开发者需要注意查看更新)
npm install
引用connect模块
//引入connect模块var connect = require("connect");//依然需要引入http模块var http = require("http")//依然使用http模块创建服务器http.createServer().listen(3000);
connect现在是http中间件,如果使用本文最新的connect版本就不能出现《了不起的node.js》一书中的connect.createServer(),因为connect已经移除了这个方法!!!
createServer交给了http模块去做,充分体现了connect是中间件的特性,而不是代替http!
依然使用http.createServer()创建http服务器!!!
使用serve-static中间件托管静态文件
node本身不像nginx,apache,他不是一个完整的http服务器,而是一个语言解析器。
通过对node.js API的开发,可以构建类似nginx一样的功能,负责提供用户静态页面,这就需要用到serve-static中间件
首先serve-static中间件原本属于connect中间件,现在已经独立出来了,也就说其实和connect关系已经不大了,但我依然将两者放在一回中解说。
静态文件指的是:html,css,图片,js等等
在项目目录下新建一个views目录,存放静态html文件
新建index.html:
<!DOCTYPE html><html lang="en"><head> ???<meta charset="UTF-8"> ???<title></title></head><body> ???<h1>hello world</h1></body></html>
index.js返回静态页面代码:
//引入http模块var http = require("http");//引入serveStatic模块var serveStatic = require(‘serve-static‘)var finalhandler = require(‘finalhandler‘);var serve = serveStatic(__dirname+‘/views‘,{‘index‘:[‘index.html‘,‘index.htm‘]});http.createServer(function(req,res){ ???serve(req,res,finalhandler(req,res));}).listen(3000);
finalhandler是什么?
作用是友好处理找不到页面访问不到URL的异常和错误,如果删除,用户访问非法地址将会导致node抛出异常停止工作。
访问首页之外的页面?
views下创建一个hello.html,直接在浏览器地址后面+“/hello.html”即可访问这个页面。
访问图片文件?
如何提示下载文件?
var contentDisposition = require(‘content-disposition‘)var finalhandler = require(‘finalhandler‘)var http = require(‘http‘)var serveStatic = require(‘serve-static‘) var serve = serveStatic(__dirname+‘/files‘, { ?‘index‘: false, ?‘setHeaders‘: setHeaders}) //强制下载function setHeaders (res, path) { ?res.setHeader(‘Content-Disposition‘, contentDisposition(path))} http.createServer(function onRequest (req, res) { ?serve(req, res, finalhandler(req, res))}).listen(3000);
新建一个files目录,下载的文件放在里面即可。
到此,serve-static中间件简化了开发者托管静态文件的代码,我们不必向上一章那样使用fs+http方式去自己实现输出流文件给浏览器客户端。
使用connect中间件处理不同的请求
在上一回中,如果要对用户不同访问给出对应的返回,我们需要在同一个createServer中不停的写if...else if....else if...else....if.........来判断,这样显得代码十分冗余,而且根本无法把所有情况写清楚,也无法扩展,且根本无法维护。
上一回的部分代码:
???//请求为图片 ???if(req.method == ‘GET‘ && req.url.substr(-4)==".jpg"){ ???????fs.stat(__dirname+req.url,function(err,stat){ ???????????if(err || !stat.isFile()){ ???????????????res.writeHead(404); ???????????????res.end("找不到图片"); ???????????????return; ???????????} ???????????serve(__dirname+req.url,‘application/x-jpg‘); ???????}); ???}else if(req.method=="GET" && req.url==‘/‘){ ???//请求为html文件 ???????serve(__dirname+‘/form.html‘,‘text/html‘); ???}else{ ???????res.writeHead(404); ???????res.end("网址丢了"); ???????return; ???}
接下来使用connect重写这些代码:(代码是基于static-serve那块修改过来的)
//下载文件模块var contentDisposition = require(‘content-disposition‘)//不需要finalhandler//var finalhandler = require(‘finalhandler‘)//http模块创建http服务器必须var http = require(‘http‘)//静态文件托管中间件var serveStatic = require(‘serve-static‘)//connect工具集var connect = require("connect");var app = connect(); app.use(function(req,res,next){ ???//任何请求都会打印!是必然执行的一步 ???console.error(‘%s %s‘,req.method,req.url) ???next();});//图片显示app.use("/images",function(req,res,next){ ???serveStatic(__dirname+"/images")(req,res,next);});//下载文件app.use("/files",function(req,res,next){ ???serveStatic(__dirname+"/files",{‘index‘:false,‘setHeaders‘:setHeaders})(req,res,next);});//最终处理,所有next都执行不通后到达此处app.use(function(req,res,next){ ???res.writeHead(404); ???res.end(‘404 Not Found‘) ???//没有next了,这是最终方法,返回404 not found就行了}); //强制下载函数function setHeaders (res, path) { ?res.setHeader(‘Content-Disposition‘, contentDisposition(path))}//建立http服务器http.createServer(app).listen(3000);
注意到我们依然需要http模块来建立http服务器,然后引入了connect模块,以及之前的serve-static和content-disposition。
这一段代码实际上是把上一回分类请求处理和这一回静态文件托管强强联手了
如果说serve-static这个中间件解决的是静态文件托管的话,那么connect中间件其实是解决了路由,请求定向的控制。这样我们不再需要通过手写的if...else来判断用户请求了什么类型的文件。
next()函数
这个函数在connect中扮演了十分重要的角色,作用:将不同种类的请求线性的串联在一行,每一个app.use()其实类似于原来我们写的if...else语句,next就是在处理不了的情况下,执行另一个app.use(),这样只要回掉函数做不了就抛给下一个做,这样不停的“甩锅”,最终谁都做不了就有一个最终函数,开发者需要建立这样的最终函数,里面没有next()了,说明到最后一步了,必须返回错误给用户看了。我们这里最终函数是一个404错误反馈。
use()函数
第一个参数可以给定一个请求的目录,相对于node执行的项目目录,相当于浏览器网址后面第一个“/”后面的参数。