社区/学习指南/微信云开发高级教程

托管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 函数的不同。

本文出自 李东bbsky