使用 Deno 创建 HTTP 服务

发表日期:
分类: 编程技术
descriptive text

今天我们来使用 Deno 创建一个 HTTP 服务,在这之前呢,我们来做一些准备工作。

Deno 介绍

对于对 Deno 不了解的同学,我们再来做一下 Deno 的介绍:

做前端的小伙伴都了解 Node.js,Deno 是和 Node.js 一样的,一个 JS/TS 运行时。 那么 Deno 的特点是什么呢:

  • Deno 基于最新的 JavaScript 语言;

  • Deno 具有覆盖面广泛的标准库;

  • Deno 以 TypeScript 为核心,配以更多独特的方式从而带来了巨大的优势,其中包括一流的 TypeScript 支持(Deno 自动编译 TypeScript 而无需你单独编译);

  • Deno 大力拥抱 ES 模块标准;

  • Deno 没有包管理器;

  • Deno 具有一流的 await 语法支持;

  • Deno 内置测试工具;

  • Deno 旨在尽可能地与浏览器兼容,例如通过提供内置对象 fetch 和全局 window 对象。

Deno 初始的目标可能是 Node.js 的替代品,旨在解决 Node.js 的一些痛点和历史遗留问题。那么它和 Node.js 有哪些异同呢?

相同点:

  • 两者都是基于 V8 引擎开发的;

  • 两者都非常适合在服务器端上编写 JavaScript 应用。

不同点:

  • Node.js 用 C++ 和 JavaScript 语言编写。Deno 用 Rust 和 TypeScript 语言编写。

  • Node.js 有一个官方的软件包管理器,称为 NPM。Deno 不会有,而会允许你从 URL 导入任何 ES 模块。

  • Node.js 使用 CommonJS 模块语法导入软件包。Deno 使用 ES 标准模块导入。

  • Deno 在其所有 API 和标准库中都使用现代 ECMAScript 功能,而 Node.js 使用基于回调的标准库,并且没有计划对其进行升级。

  • Deno 通过权限控制提供了一个安全的沙箱环境,程序只能访问由用户设置为可执行标志的文件。Node.js 程序可以直接访问用户足以访问的任何内容。

  • Deno 长期以来一直在探索将程序编译成单个可执行文件的可能性,从而使得该可执行文件可以在没有外部依赖项(例如 Go)的情况下运行,但这并不是一件容易的事,如果做得到,将会成为更有话语权的游戏规则改变者。

  • Deno 没有 npm 一样的包管理器。

安装

Deno 的安装其实很简单。

windows 安装

windows 环境,官方推荐使用 Chocolatey 安装。

首先安装 Chocolatey

那么首先,我们需要安装 Chocolatey。

用管理员模式运行 cmd,我们可在 C:\Windows\System32 下找到 cmd.exe 右键管理员运行。

输入以下命令:

@"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -InputFormat None -ExecutionPolicy Bypass -Command "iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))" && SET "PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin"

安装完毕后,使用 choco -v 查看是否安装成功。

安装 deno

choco install deno

使用 deno -V 查看是否安装成功。

Mac 安装

Mac 安装最简单的方法是使用 Homebrew:

sh brew install deno 安装完毕后,使用 deno -V 查看是否安装成功。

安装 Vscode 扩展

启动一个 Deno 服务

首先新建一个 index.ts 文件,写入以下代码

使用deno run index.ts 命令运行它。

先下载了一些依赖项,最后在编译的步骤上报错了,提示我们没有网络权限,这其实是 Deno 的 安全沙箱 Sandbox 的问题。Deno 有一个安全沙箱,可以防止程序做一些你不允许的事情。 那么我们在上面的运行命令上加上 --allow-net

deno run --allow-net app.ts

运行成功了,该应用程序现在监听在 8000 端口上运行着 HTTP 服务器:

构建 REST-API

我们启动了一个 Deno 服务,那么如果我们想编写一套符合 REST 标准的 API,怎么办呢。我们知道,Node.js 拥有 Express、Koa、egg 等优秀的开源框架。Deno 同样有人开发了一个 Oak 框架,从名字上我们就能看出来,它是一个类似于 Koa 的中间件框架。

那么我们接下来构建一个这样的 API:

  • 添加一本书
  • 获得书的列表
  • 获得某本书的详情
  • 删除一本书
  • 更新一本书

创建一个 app.ts 文件。

从 Oak 导入 Application 和 Router 对象:

import { Application, Router } from "https://deno.land/x/oak/mod.ts";

然后我们得到环境变量 PORT 和 HOST:

const env = Deno.env.toObject();
const PORT = env.PORT || 8000;
const HOST = env.HOST || "127.0.0.1";

编写应用代码

使用命令来运行它

deno run --allow-env --allow-net app.ts

接下来我们来编写我们的 API 吧。

在文件的顶部,让我们定义一个 BOOK 的接口,然后我们声明一个初始的 books 数组 Book 对象。

interface Book {
  name: string;
  author: string;
  introduction: string;
}

let books: Array<Book> = [
  {
    name: 'bookOne',
    author: '张三',
    introduction: '这是这本书的介绍',
  },
  {
    name: 'bookTwo',
    author: '李四',
    introduction: '这是这本书的介绍',
  },
]

安装 REST API 的规范定义好我们的结构

  • GET /books

  • GET /books/:name

  • POST /books

  • PUT /books/:name

  • DELETE /books/:name

那么代码这么写:

router
  .get('/books', getBooks)
  .get('/books/:name', getBook)
  .post('/books', addBook)
  .put('/books/:name', updateBook)
  .delete('/books/:name', deleteBook)

接下来写每个接口的方法:

GET /books 获取所有书籍列表

// 获得所有书的列表
export const getBooks = ({ response }: { response: any }) => {
  response.body = books
}

GET /books/:name 通过名字来获取某一本书的详情

// 获取某一本书
export const getBook = ({
  params,
  response,
}: {
  params: {
    name: string,
  },
  response: any,
}) => {
  const book = books.filter((book) => book.name === params.name)
  if (book.length) {
    response.status = 200
    response.body = book[0]
    return
  }
  response.status = 400
  response.body = { msg: `Cannot find book ${params.name}` }
}

POST /books 添加一本书

// 添加一本书
export const addBook = async ({ request, response }: { request: any, response: any }) => {
  const body = await request.body()
  const book: Book = body.value
  books.push(book)
  response.body = { msg: 'OK' }
  response.status = 200
}

PUT /books/:name 更新一本书

// 更新一本书
export const updateBook = async ({
  params,
  request,
  response,
}: {
  params: {
    name: string,
  },
  request: any,
  response: any,
}) => {
  const temp = books.filter((existingBook) => existingBook.name === params.name)
  const body = await request.body()
  const { introduction }: { introduction: string } = body.value
  if (temp.length) {
    temp[0].introduction = introduction
    response.status = 200
    response.body = { msg: 'OK' }
    return
  }
  response.status = 400
  response.body = { msg: `Cannot find book ${params.name}` }
}

DELETE /books/:name 删除一本书

// 删除一本书
export const deleteBook = ({
  params,
  response,
}: {
  params: {
    name: string,
  },
  response: any,
}) => {
  const lengthBefore = books.length
  books = books.filter((book) => book.name !== params.name)
  if (books.length === lengthBefore) {
    response.status = 400
    response.body = { msg: `Cannot find book ${params.name}` }
    return
  }
  response.body = { msg: 'OK' }
  response.status = 200
}

好,现在所有的接口都写完了,我们可以用 postman 来进行测试。

  • 获取所有书的列表

  • 获取某一本书的详情

  • 添加一本书

再次查询,发现刚才添加的书已经在列表中了。

  • 修改一本书

再次查询,发现 bookOne 的简介更新了。

  • 删除一本书

再次查询,发现 bookOne 已经从列表中删除了

以上,我们已经完成了我们的 REST API 了。可以看到 Deno 写代码与 Node.js 并没有太大的差异,不同的在于 Deno 的依赖管理方式,下一篇文章我们将讲一讲 Deno 项目如何管理引入依赖,以及它的优缺点。

本文所有代码可见:Github



本文作者:李瑞丰


本文采用 知识共享署名 4.0 国际许可协议 (CC BY 4.0),欢迎转载、或重新修改使用,但需要注明来源。


你的鼓励是我最大的支持,你可以在 知乎掘金 等平台关注我,也可以关注我的公众号 「SayHub」 获取更多内容。