当前位置: 首页 > 科技观察

使用Node.js原生API编写Web服务器

时间:2023-03-13 08:49:42 科技观察

Node.js是一门在JavaScript基础上发展起来的语言,所以前端开发者应该是天生的。一般我们会把它作为CLI工具或者Web服务器使用,Web服务器有很多成熟的框架,比如Express、Koa。但是Express和Koa都封装了Node.js的原生API,所以实际上我们可以不借助任何框架只用原生API来写一个web服务器。这篇文章要讲的是如何在不使用框架的情况下,用原生API编写一个Web服务器。因为在我的计划中,后面会写Express和Koa的源码分析,都是使用原生API实现的。所以这篇文章其实就是这两个源码分析的前置知识,可以帮助我们更好的理解Express、Koa等框架的含义和源码。本文只是为了说明原生API的使用,代码比较丑陋,实际工作中请勿模仿!本文可运行代码示例已上传到GitHub,大家可以拿下来玩:https://github.com/dennis-jiang/Front-End-Knowledges/tree/master/Examples/Node.js/HttpServerHelloWorld要搭建一个简单的web服务器,使用原生的http模块就可以了,简单的HelloWorld程序几行代码就够了:consthttp=require('http')constport=3000constserver=http.createServer((req,res)=>{res.statusCode=200res.setHeader('Content-Type','text/plain')res.end('HelloWorld')})server.listen(port,()=>{console.log(`Serverisrunningonhttp://127.0.0.1:${port}/`)})这个例子很简单,直接使用http.createServer创建一个服务器,这个服务器没有任何逻辑,只是返回HelloWorld访问时。服务器创建完成后,使用server.listen在3000端口运行。这个例子真的很简单,但是好像除了输出一个HelloWorld,什么也做不了。与我们一般使用的web服务器相去甚远,主要有以下几点:不支持HTTP动词,如GET。POST不支持路由,没有静态资源托管,不能持久化数据。前三点是Web服务器必不可少的基本功能。现在还有各种各样的微服务和数据库打交道来做持久化,但是我们也应该知道如何做持久化。那么我们来写一个真正可用的web服务器,也就是把缺的地方补上。我们在路由和HTTP动词前面的HelloWorld并不是完全不能用,因为代码位置还是要在http.createServer里面,我们会在里面添加路由功能。为了区别于下面的静态资源,我们的API请求都是以/api开头的。做路由匹配并不难。最简单的方法就是直接用if条件判断。为了得到请求地址,我们需要使用url模块来解析传递过来的地址。Http动词可以直接通过req.method获取。所以http.createServer修改如下:consturl=require('url');constserver=http.createServer((req,res)=>{//获取url的所有部分//url.parse可以将req.url解析成一个Object//consturrlObject=url.parse(req.url);const{pathname}=urlObject;//api以API请求开头if(pathname.startsWith('/api')){//再次判断路由if(pathname==='/api/users'){//获取HTTP动词constmethod=req.method;if(method==='GET'){//写一个假数据constresData=[{id:1,name:'Xiaoming',age:18},{id:2,name:'Xiaohong',age:19}];res.setHeader('Content-Type','application/json')res.end(JSON.stringify(resData));return;}}}});现在我们可以通过访问/api/users获取用户列表:支持静态文件据说API请求以/api开头,也就是不以这个开头的可以认为是静态文件,不同的文件有不同的内容类型。在我们的例子中,暂时只支持一种.jpg。实际上,只需在我们的if(pathname.startsWith('/api'))中添加一个else。返回静态文件需要:使用fs模块读取文件。返回文件时,根据不同的文件类型设置不同的Content-Type。所以我们的else是这样的://...省略...else前后的代码{//使用path模块获取文件后缀名constextName=path.extname(pathname);if(extName==='.jpg'){//使用fs模块读取文件fs.readFile(pathname,(err,data)=>{res.setHeader('Content-Type','image/jpeg');res.write(data);res.end();})}}然后我们在同级目录下放一张图试试:数据持久化数据持久化有几种方式,一般都是存储在数据库中,少数情况下,文件也被存储。保存数据库很麻烦,需要创建并连接到数据库。这里没有很好的demo,我们来演示一个保存文件的例子。通常,POST请求用于存储新数据。我们在上一个的基础上增加一个POST/api/users来添加一条数据。我们只需要在前面的if(method==='GET')后面加上一个POST判断就可以了://...省略其他代码...elseif(method==='POST'){//注意数据传输中可能有多个chunk//我们需要拼接这些chunkletpostData='';req.on('data',chunk=>{postDatapostData=postData+chunk;})req.on('end',()=>{//数据传输后将内容插入db.txtfs.appendFile(path.join(__dirname,'db.txt'),postData,()=>{res.end(postData);//写入数据后,会再次返回数据});})}然后我们测试一下这个API:看再看看文件里有没有写:综上所述,我们完成了一个基本功能的web服务器。代码并不复杂,但是对于我们理解Nodewebserver的原理还是很有帮助的。但是上面的代码还有一个很大的问题:代码丑!所有代码都写成一堆,HTTP动词和路由匹配都是用if条件判断。几百个API,十多个动词,代码简直就是一场灾难!因此,我们应该将路由处理、HTTP动词、静态文件、数据持久化等所有功能抽离出来,让整个应用更加优雅和可扩展。这就是Express、Koa等框架存在的意义。下一篇我们会去Express的源码看看他是怎么解决这个问题的。注意不要迷路~