14.6 托管Nodejs Server

14.6.1 Nodejs Server的基础知识

1、基础的HTTP服务器

只要在本地或服务器安装了Node环境,使用 require('http')引入http模块,就能用http.createServer()方法创建一个服务器。比如我们使用VS Code新建一个app.js的文件(保存在电脑的到哪里都行),然后输入以下代码:

const http = require('http'); //引入内置的http模块 const hostname = '127.0.0.1'; const port = 3000; const requestHandler = (req, res) => { // res.statusCode = 200; res.setHeader('Content-Type', 'text/html;charset=utf-8'); res.end('Node Server创建成功啦'); console.log(`请求链接是:${req.url},请求方法是:${req.method}`) } const server = http.createServer(requestHandler) //使用 http.createServer() 方法创建服务器 server.listen(port, hostname, () => { //listen为createServer返回对象的方法,用于指定HTTP服务器监听的端口号 console.log(`通过此链接访问服务器 http://${hostname}:${port}/`); });

保存后,在VS Code里右键该文件选择在终端中打开,然后在VS Code的终端中输入以下代码按Enter执行:

node app.js

在浏览器里输入http://127.0.0.1:3000/,就能访问我们创建好的服务器啦,页面也会显示Node Server创建成功啦,可以说使用Nodejs创建一个HTTP服务器非常容易。

注意requestHandler有两个参数,req是request请求对象,调用request对象就可以拿到所有HTTP请求的信息,比如request.url获取请求路径;res是response响应对象,调用response对象的方法,就可以把HTTP响应返回给浏览器了。当用户每访问一次(比如刷新一下页面)就会触发requestHandler函数,我们也能在终端看到打印的日志。

2、文件服务器

借助于fs 模块: 可以对文件目录进行创建、删除、查询以及文件的读取和写入以及url模块: 可以处理与解析 URL,我们可以把服务器里的文件发送给客户端。比如我们可以修改一下app.js的代码为如下:

var http = require('http'); var url = require('url'); var fs = require('fs'); http.createServer( (req, res) => { //这里把上面的requestHandler给整到一起了,注意一下 const requrl = url.parse(req.url, true); const filename = "." + requrl.pathname; //这里的.表示的是相对路径 fs.readFile(filename, function(err, data) { if (err) { res.writeHead(404, {'Content-Type': 'text/html;charset=utf-8'}); return res.end("404 页面没有找到"); } res.writeHead(200, {'Content-Type': 'text/html;charset=utf-8'}); res.write(data); console.log(`请求链接是:${req.url},请求方法是:${req.method}`); return res.end(); }); }).listen(3000);

然后再在终端执行node app.js(如果你之前的node server还在执行,你可以连续按两次Ctrl+C来终止服务器,再来执行node app.js)。放一个文件比如tcb.jpg到app.js的相同目录里,在浏览器里输入如下地址(也就是ip地址+文件的路径)看看:

http://127.0.0.1:3000/tcb.jpg

本地调试时,服务器和客户端都是同一条电脑,我们使用浏览器打开http://127.0.0.1:3000/就能通过浏览器访问到服务器里的文件。

14.6.2 托管Nodejs Server

那云函数是否可以搭建一个Nodejs的服务器呢,结合云接入和云函数,可以很轻松地托管Nodejs服务端程序。这里就要使用到serverless-http的模块。我们可以使用serverless-http把集成请求转化为 Node.js Server 能接收的 IncommingMessage,同时把返回的 ServerResponse 转化为集成请求。 使用VS Code在functions文件夹里新建一个云函数,比如server,和小程序云开发的云函数一样新建3个文件,以及assets文件夹,里面存放我们要返回的HTML文件、图片等各种静态资源,结构如下:

├── server //server云函数目录 │ └── assets │ └── index.html │ └── demo.jpg │ └── index.js │ └── config.json │ └── package.json

并在package.json里添加serverless-http依赖,

"dependencies": { "wx-server-sdk": "latest", "serverless-http": "latest" }

然后再在index.js里输入以下代码,我们把之前Nodejs Server里的代码Copy过来,注意与普通云函数和集成请求写法的不同。

const cloud = require('wx-server-sdk') const url = require('url') const fs = require('fs') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) const serverless = require('serverless-http'); const requestHandler = (req, res) => { // const requrl = url.parse(req.url, true); const filename = "." + requrl.pathname; //这里的.表示的是相对路径 fs.readFile(filename, function(err, data) { if (err) { res.writeHead(404, {'Content-Type': 'text/html;charset=utf-8'}); return res.end("404 页面没有找到"); } res.writeHead(200, {'Content-Type': 'text/html;charset=utf-8'}); res.write(data); console.log(`请求链接是:${req.url},请求方法是:${req.method}`); return res.end(); }); } exports.main = serverless(requestHandler);

终端进入云函数目录server文件夹,使用npm install安装依赖,然后再回退到项目根目录使用CLoudbase Cli命令将云函数部署到云端并创建server云函数云接入的路由,再用浏览器或cURL命令访问云接入链接:

cloudbase functions:deploy server cloudbase service:create -p /server -f server

这样我们就能通过云接入的链接来访问托管的服务器里面的资源了,只要是云函数目录里面的资源就都能访问,云函数就”化身“成了一个服务器了。比较一下集成请求返回html与托管Nodejs Server的不同

https://xly-xrlur.service.tcloudbase.com/server/assets/index.html

14.6.3 托管 Koa.js

Koa和Express都是基于Nodejs平台的web应用开发框架,可以对HTTP Request对象和HTTP Response对象进行封装处理,以及生命周期的维护,路由、视图的处理等等。云接入和云函数可以托管Nodejs Server,它也支持托管Koa和Express,下面就以Koa为例。

1、托管Koa

我们还是以server云函数为例,首先在server云函数的package.json里添加koa依赖,然后将index.js改为如下代码,仍然读取云函数目录下的assets文件里的index.html文件:

const cloud = require('wx-server-sdk') const fs = require('fs') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) const serverless = require('serverless-http'); const Koa = require('koa'); const app = new Koa(); app.use(async ctx => {//ctx是由koa传入的封装了request和response的变量,通过它可以访问request和response ctx.response.type = 'text/html;charset=utf-8'; //ctx.response就是Node的response对象 ctx.response.body = fs.createReadStream('./assets/index.html'); }) exports.main = serverless(app);

进入云函数目录下载云函数的依赖之后,回退到项目根目录部署上传server云函数,再使用浏览器打开server云函数的云接入地址就能看到解析好的index.html了。

Koa 的Context 上下文将 node 的 request 和 response 对象封装到单个对象中,为编写 Web 应用程序和 API 提供了许多有用的方法。为方便起见许多上下文的访问器和方法直接委托给它们的 ctx.request或 ctx.response。ctx.response就是Node的response对象,ctx.request就是Node的request对象。

2、Koa原生路由

使用Koa也能让云函数+云接入作为文件服务器,把云函数目录下的文件都返回给浏览器,我们将server云函数的代码修改为如下,这个功能和前面的托管Nodejs Server类似:

const cloud = require('wx-server-sdk') const fs = require('fs') const url = require('url') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) const serverless = require('serverless-http'); const Koa = require('koa'); const app = new Koa(); app.use( async ( ctx ) => { const requrl = url.parse(ctx.request.url, true); const filename = "." + requrl.pathname; ctx.response.type = 'text/html;charset=utf-8'; ctx.body =fs.createReadStream(filename) }) exports.main = serverless(app);

将Server云函数部署上传,和前面一样我们可以在浏览器里输入以下地址来访问server云函数目录里的assets文件夹里的index.html页面:

https://xly-xrlur.service.tcloudbase.com/server/assets/index.html

Koa原生路由通过解析request IncomingMessage 的 url 属性, 利用 if...else 来判断路径返回不同的结果,但是如果路由过多, if...else 的分支也会越庞大, 不利于代码的维护,具体的案例这里就不多写了,下面直接用Koa-router解决方案。

14.6.4 Koa router中间件

尽管我们可以依靠ctx.request.url这种比较原生的方式来手动处理路由,但是这会写很多处理代码,这时候就需要对应的路由中间件对路由进行控制,这里推荐使用Koa-router,以及推荐使用koa-bodyparser中间件。对于POST请求的处理,koa-bodyparser可以把ctx的formData数据解析到ctx.request.body中。

1、使用Koa router中间件

我们仍然以server云函数为例,在package.json添加如下依赖,并进入server云函数目录下载这些依赖:

"dependencies": { "wx-server-sdk": "latest", "serverless-http": "latest", "koa":"latest", "koa-bodyparser":"latest", "koa-router":"latest" }

然后将server云函数修改为如下代码,然后部署上传server云函数,然后访问云接入的地址,注意打开的页面里的首页关于我们是可以点击的,会跳转到koa-router指定的路由,并返回相应的内容:

const fs = require('fs') const cloud = require('wx-server-sdk') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) const serverless = require('serverless-http'); const Koa = require('koa'); const bodyParser = require('koa-bodyparser') const app = new Koa(); const Router = require('koa-router') const router = new Router() app.use(bodyParser()) router.get('/',async (ctx) => { //注意这里的路径哈,server为云接入的路由,在前面我们把server云函数的路由设置为server let html = ` <ul> <li><a href="/server/home">首页</a></li> <li><a href="/server/about">关于我们</a></li> </ul> ` ctx.body = html }) router.get('/home',async (ctx) => { ctx.response.type = 'text/html;charset=utf-8'; ctx.response.body = fs.createReadStream('./assets/index.html'); }) router.get('/about', async ( ctx )=>{ ctx.body = '欢迎您的关注,网页还在建设当中' }) app.use(router.routes()) // 添加路由中间件 app.use(router.allowedMethods()) // 对请求进行一些限制处理 exports.main = serverless(app);

当我们打开云接入地址/home时,返回的是云函数目录下的assets文件夹里的index.html页面,而事实上云函数目录并没有home这个文件夹,按照之前的方式打开云接入地址/assets/index.html也打不开了,这个就是路由重定向

这个案例仅仅只是使用了GET方法来进行注册路由,我们还可以使用POST、DELETE、PUT、DEL、ALL等方法。而koa-router路由也还支持变量等,这里就不展开啦。

2、路由中间件与云开发

有了路由中间件,我们就能把最常见的GET、POST请求都集成在一个云函数里,比如数据库、云存储的增删改查,从而将该云函数作为API服务器,向前端返回所需数据和执行指定的操作。在小程序端我们可以使用wx.request()接口发起HTTPS网络请求(值得注意的是小程序端需要将云接入的域名添加到小程序里的域名白名单内),在Web端则可以通过axios。

获取数据库里的数据 比如我们用Koa router可以添加一个路由getData,用来返回云数据库查询到的数据结果:

router.get('/getData',async (ctx) => { ctx.body = await db.collection('china').where({ gdp: _.gt(3000) }) .field({ _id:false, city: true, province: true, gdp:true }) .orderBy('gdp', 'desc') .skip(0) .limit(10) .get() })

在小程序端获取返回数据:

wx.request({ url: 'https://xly-xrlur.service.tcloudbase.com/server/getData', success (res) { console.log(res.data.data) } })

在web端获取返回数据:

const url ="https://xly-xrlur.service.tcloudbase.com/server/getData" axios.get(url).then(res => { console.log(res.data) }).catch(err => { console.log(err) })

往数据库里添加数据库 我们还可以使用Koa router提供POST接口,对前端传来的参数、数据进行处理,比如我们可以往数据库里添加数据,只需要注意Koa是如何获取参数和数据的即可。

router.post('/addData',async (ctx) => { const {userInfo} = await ctx.request.body const addUser = await db.collection('china').add({ data:userInfo }) ctx.body = addUser })

小程序端发起POST请求的代码如下:

wx.request({ url: 'https://xly-xrlur.service.tcloudbase.com/server/addData', method:"POST", data:{ userInfo:{ Name: '腾讯云云开发', enName: 'CloudBase' } }, success (res) { console.log(res) } })

在web端发起POST请求的代码如下:

async function addData(){ axios.post('https://xly-xrlur.service.tcloudbase.com/server/addData', { userInfo:{ Name: '腾讯云云开发', enName: 'CloudBase' } }) .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); }); }

我们还可以在小程序端或Web端调用一下server云函数,看看返回的数据对象和以往的有什么不同?大致了解一下后台函数HTTP函数的不同。