新近发布轻量级的 Node.js 服务端框架Fastify怎么样

新近发布轻量级的 Node.js 服务端框架Fastify怎么样,第1张

restify 是一个基于Nodejs的REST应用框架,支持服务器端和客户端。 restify 比起 express 更专注于 REST 服务,去掉了 express 中的 template , render 等功能,同时强化了 REST 协议使用,版本化支持, HTTP 的异常处理。

Github: node-restify

API: API DOCUMENTATION

restify安装

新建项目

mkdir nodejs_restify

cd nodejs_restify

npm install restify

第一步,用node输出一个hello world

    var http=require('http') 

    http.createServer(function(req,res){ 

        var urlPares=url.parse(req.url) 

        var query=querystring.parse(urlPares.query) 

        res.end('hello world') 

    }).listen(80)

大部分的node教程在这里会告诉你,我们很容易的建立的一个服务器。但是在实际使我们通常使用的是express.(f**k,难道Node必须要用express吗?自己实现一个Web应用框架真的很难吗?)其实并不是。

那么既然打算自己写我们首先要知道我们要做哪些事情。 1.路由或者智能路由 2.静态文件输出 3.session/cookie 4.模版渲染 5.数据库处理 6.文件上传

第二步,路由

路由好高大上的名字,它是干啥的?url对应具体方法就是它该做的事情。 那么我们为什么不让url对应xxx文件的xx方法。 例如:/user/login能不能自动对应到user.js的login方法上。实现起来很难么?其实只需要几句代码

   

 var fs = require("fs") 

    module.exports=function(req,res){ 

        var query=req.query 

        var urlPares=req.urlPares 

        var pathname=urlPares.pathname 

        var arr=pathname.split("/") 

        req.arr=arr 

        //start 这段代码处理默认行为。可以先忽略 

        if(arr.length==0||arr.length==1){ 

            arr=["","index","index"] 

        }else if(arr.length==2){ 

            arr.push("index") 

        } 

        if(arr[1]==""){ 

            arr[1]="index" 

        } 

        if(arr[2]==""){ 

            arr[2]="index" 

        } 

        //end 这段代码处理默认行为。可以先忽略 

        if (fs.existsSync(APP_PATH+'/controller/'+arr[1]+'.js')){ 

            var controller=require('./controller/'+arr[1]) 

            if(controller[arr[2]]){ 

                controller[arr[2]](req,res) 

            }else{ 

                res.writeHead(404,{'Content-Type': 'text/plain' }) 

                res.end("你访问的控制器不存在指定方法") 

            } 

        }else{ 

            res.writeHead(404,{'Content-Type': 'text/plain' }) 

            res.end("你访问的路径不存在") 

        } 

    }

通过fs判断文件是否存在。然后去require它就行了。APP_PATH是个全局变量表示程序入口的路径。

第三步,静态文件输出

静态文件输出我们需要一个库MIME

  

  var url = require("url") 

    var fs = require("fs") 

    var mime = require('mime') 

    /** 

     * [[检测是否为静态资源]] 

     * @param   {Object}   req [[Description]] 

     * @param   {[[Type]]} res [[Description]] 

     * @returns {bool} [[Description]] 

     */ 

    module.exports = function (req, res) { 

        //正则表达式检测文件后缀 

        var url_resource_reg = /.*\.(html|htm|gif|jpg|jpeg|bmp|webp|htc|swf|png|ico|txt|js|css)/ 

        if (!url_resource_reg.test(req.url)) { 

            return false 

        } 

        var urlPares = url.parse(req.url) 

        var pathname = urlPares.pathname 

        var fileUrl = APP_PATH + "/static" + pathname 

        if (fs.existsSync(fileUrl)) { 

            var contentType = mime.lookup(fileUrl) 

            res.setHeader('Content-Type', contentType || "text/plain") 

            var fileStream = fs.createReadStream(fileUrl) 

            fileStream.pipe(res) 

            fileStream.on('end', function () { 

                res.end() 

            }) 

            return true 

        } else { 

            return false 

        } 

    } 

第四步,session/cookie

这里稍微有点。但是代码量也不多

    var sessions = {} 

    var sessionKey = 'session_key' 

    var EXPIRES = 30 * 60 * 1000 

    function randString(size) { 

        var result = '' 

        var allChar = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' 

        size = size || 1 

        while (size--) { 

            result += allChar.charAt(rand(0, allChar.length - 1)) 

        } 

        return result 

    } 

    var generate = function () { 

        var session = {} 

        session.id = Date.now() + randString(12) 

        session.cookies = { 

            expire: Date.now() + EXPIRES 

        } 

        sessions[session.id] = session 

        return session 

    } 

    var parseCookie= function (cookie) { 

        var cookies = {} 

        if (!cookie) { 

            return cookies 

        } 

        var list = cookie.split("") 

        for (var i = 0 i < list.length i++) { 

            var pair = list[i].split("=") 

            cookies[pair[0].trim()] = pair[1] 

        } 

        return cookies 

    } 

    var serializeCookies = function (cookies) { 

        var arr = [] 

        for (var key in cookies) { 

            arr.push(serialize(key, cookies[key])) 

        } 

        return arr 

    } 

    var serialize = function (name, value, option) { 

        var pairs = [name + '=' + encodeURI(value)] 

        //设置cookie默认共用"/"路径 

        option = option || { 

            path: "/" 

        } 

        if (option.maxAge) pairs.push('Max-Age=' + option.maxAge) 

        if (option.domain) pairs.push('Domain=' + option.domain) 

        if (option.path) pairs.push('Path=' + option.path) 

        if (option.expires) pairs.push('Expires=' + option.expires) 

        if (option.httpOnly) pairs.push('HttpOnly') 

        if (option.secure) pairs.push('Secure') 

        return pairs.join(' ') 

    } 

    module.exports = function (req, res) { 

        req.cookies = parseCookie(req.headers.cookie) 

        var id = req.cookies[sessionKey] 

        if (!id) { 

            req.session = generate() 

        } else { 

            var session = sessions[id] 

            if (session) { 

                if (session.cookies.expire > Date.now()) { 

                    session.cookies.expire = Date.now() + EXPIRES 

                    req.session = session 

                } else { 

                    delete sessions[id] 

                    req.session = generate() 

                } 

            } else { 

                req.session = generate() 

            } 

        } 

        for (var key in sessions) { 

            if (sessions[key].cookies.expire < Date.now()) { 

                delete sessions[key] 

            } 

        } 

        var writeHead = res.writeHead 

        res.writeHead = function () { 

            delete req.cookies[ham_sessionKey] 

            var sessionStr = serialize(ham_sessionKey, req.session.id) 

            res.setHeader('Set-Cookie', serializeCookies(req.cookies).concat(sessionStr)) 

            return writeHead.apply(res, arguments) 

        } 

    }

第五步,模版渲染

这是最简单的。

第六步,数据库处理

这里可以是用一些ORM框架。

第七步,文件上传,post

第八步,就是你把上面的代码组织起来。

程序 or 框架?

程序是已经成型的应用,你需要的是为它搭建环境、添加配置,然后就可以运行起来;框架则是应用的骨架,你需要为它添加数据模型、业务逻辑,它才能成为应用,开始提供服务。

事实上,对于Web开发来说,程序和框架的区别正越来越模糊,比如几乎妇孺皆知的Wordpress,它是一个博客程序,但它丰富的插件以及高度的 自定义能够支持很大程度上的二次开发,在这点上它比起一些PHP框架也并不逊色。我个人认为,如果重心在于提供服务而不是掌握技术,有WordPress 这样的程序是没有必要使用框架的。

可惜的是,由于Nodejs还很年轻,目前还没有WordPress这样的程序,因此目前在Node.js开发里,如果想做出自己想要的作品,框架是必然的选择。如果是某些特定类型的应用,可以尝试一些开源的程序,比如要用Nodejs做博客,有Hexo、Ghost等。

回到顶部

Node.js Web框架有哪些?

Node.js里的Web框架分为API框架和Web应用框架。前者能够开发出RESTful的API,后者也能开发出RESTful API,但还包括模板、渲染等为前端所准备的功能。

API框架的使用场景是为跨平台应用提供统一的数据模型,而渲染由前端/客户端自行解决。目前比较知名的API框架有

restify(文档、Github、NPM)

ActionHero.js(官网、Github、NPM)

LoopBack(官网、Github、NPM)

Frisby(官网、Github、NPM)

Fortune.js(官网、Github、NPM)

Web应用框架顾名思义,就是为了打造Web应用所开发的框架。这里有两种风格的Web应用框架。

一个是Sinatra风格,另一个是Rails风格。Sinatra和Rails都是Ruby语言的Web框架,后者的影响力更大也更为知名。这里简单的解释一下两种风格是什么意思。

Sinatra风格是指高度可配置,注重开发的自由度。代表性的Nodejs Web框架有:

Express(官网、Github、NPM)TJ大神开发,Node.js官方推荐

hapi(官网、Github、NPM)

koa.js(官网、Github、NPM)

flaliron(官网、Github、NPM)

total.js(官网、Github、NPM)

locomotive(官网、Github、NPM)

Rails风格则是指不重复自己和约定优于配置,以及严格遵循MVC结构开发。代表性的框架有:

Sails.js(官网、Github、NPM)

geddy(官网、Github、NPM)

CompoundJS(官网、Github、NPM) 原railswayjs

这两种风格无所谓谁优谁劣,全凭使用者的偏好。

而在这两种Web框架之外,还有更大型的框架,即全栈框架,其中的代表是MEAN。

回到顶部

MEAN?

MEAN指MongoDB+Express+Angular.js+Node.js,这一组合包括运行环境、数据库、Web框架和前端引擎。被称为 全栈框架(Full-stack framework)。这其中除了Node.js之外,每一个都是可替换的,目标是创建从前端到后端,全部使用javascript的Web应用。

由于这一框架的完善性,有人将其称为LAMP的接班人。LAMP即PHP的典型运行环境,Linux+Apache+MySql+PHP,被大量的用于各种虚拟主机上。

MEAN看似庞大,但事实上要构建完整的现代化Web应用,特别是SPA(单页面应用),这几个组件都是难以缺少的,并且,其中每一项几乎都是目前 情况下的最佳选择,因此用于学习和重头开始打造新的Web应用是非常合适的。但由于实际业务的独特性,很可能要替换其中的组件,比如用Mysql来替换 MongoDB,因此,学习其中的原理和架构,打造自己的类MEAN框架也是一种选择。

作为个人和小团队来说,全栈框架MEAN基本上足够了,但目前大多数全栈框架还包含一项特性,那就是实时,拥有实时功能的框架我们又称为实时框架。

回到顶部

实时框架好吗?

实时框架(Real-time framework)指包含了webSocket的双向通信功能,能够在服务器和客户端做到实时通信的框架。

服务端和客户端自由通信的需求一直都在,但由于HTTP协议本身的局限性,因此催生了Comet等变通的方法,但即使这样也离实时相距甚远。而当 Node.js兴起后,另一个HTML5技术webSocket也渐渐成熟,人们突然发现,实时通信一下子变得触手可及,于是webSocket技术在 Node.js中得到大量的应用,其中最为知名的模块就是socket.io,而各种全栈框架也纷纷加入实时特性来应对更广阔的开发需求。

目前有代表性的实时框架有:

Meteor(官网、Github、NPM)

MEAN.io(官网、Github、NPM)

Derby(官网、Github、NPM)

SocketStream(官网、Github、NPM)

不过说实话,目前能看到的实时通信的应用场景其实不多,其中大多集中于聊天室、to-do、实时图表、在线游戏等领域。其他领域使用实时特性不但没必要,而且是对服务器资源的浪费。因此目前是否要采用实时框架,要看具体的项目而定。

以上基本就是Node.js Web框架的现状了,相信看到这里,对于选择何种框架读者已经心里有数了吧。最后再介绍一个容易搞混的概念,和解释一下我的选择。

回到顶部

YEOMAN?

第一次见到这个词,我还以为它和MEAN有什么联系。事实上,它们是截然不同的两个东西。YEOMAN由YO(脚手架)、grunt(构建工具)、bower(包管理器),它代表的是一种工作流,与框架开发的思维方式完全不同。具体的介绍可见这里。

YEOMAN能够和框架达到类似的目的,都是为构建一个Web应用做好准备,但是要不要采用YEOMAN,则是见仁见智。我个人的看法是,学习 YEOMAN本身就需要不少时间,并且有一定的学习门槛。至少在目前,使用框架开发还是相对经济的,而如果以后YEOMAN这种模式推广开来,再来学习也 不迟,更何况有一定的Node.js项目经验之后再来学习YEOMAN要轻松很多。

事实上,我还是很认可YEOMAN这种Generator+package Manager的模式的,这是因为Node.js本身崇尚微模块的 概念,即无论是多么小的功能,都将它们模块化,甚至大的模块也要拆分成小的模块,然后通过搭积木的方式来构建应用。这样能够彻底的解耦,对于不容易调试的 Javascript来说,也有助于定位和修复应用中的问题。Generator就是这种理念催生下的产物,通过选择不同的配置和选项,将积木搭起来。不 过对于这种模式目前大家也还处于实验当中,不急于进行实际应用。

回到顶部

为什么我选择了Hackathon Starter?

在我的个人项目中,使用的是Hackathon Starter,一个Node.js Web应用脚手架。

我使用它的原因是,要求高度可配置,同时又讨厌写一些配置的代码,因此它对于我来说是很好的选择。一些全栈框架对我来说,封装过多,将原生的 Node.js/Express API隐藏掉了,要使用还需要一定的学习成本。而Express这样的框架又太过简洁,在实际的项目中使用还需要大量的插件和配置,而这些在 Hackathon Starter中都已经帮我们做好了,同时还有一些示例代码以供学习,对于新人来说非常友好,可以避免过多的挫折感。


欢迎分享,转载请注明来源:夏雨云

原文地址:https://www.xiayuyun.com/zonghe/493323.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2023-06-14
下一篇2023-06-14

发表评论

登录后才能评论

评论列表(0条)

    保存