使用Koa2进行Web开发(一)

2023-10-30

这篇文章是我正在进行写作的《新时期的Node.js入门》的一部分

Connect,Express与Koa

为了更好地理解后面的内容,首先需要梳理一下Node中Web框架的发展历程
Connect
在connect官方网站提供的定义是,

Connect is a middleware layer for Node.js

开发者可以认为connect是一个Node中间件的脚手架。
Connect的源码结构十分简单,只有一个文件,去掉注释后的代码不超过两百行。
我们之所以首先提到connect,是因为connect首先在Node服务器编程中引入了middleware的概念,这种概念在当时(2010年)无疑是超前的,中间件概念的引入,将web开发变成了一个个独立的模块,使得社区的开发者们可以专注在中间件模块的开发上,
为后面express的诞生与繁荣打下了坚实的基础。
此外,connect还提出了一些关于中间件使用的规范,例如使用use方法加载中间件并且通过next方法调用中间件等

Express的诞生
Express框架是在Connect的基础上扩展而来的,它继承了connect的大部分思想,
Express的发展分为两个阶段,express3.x与express4.x
在3.x中,express依赖与connect的源码,并且内置了不少中间件,这种做法的缺点是如果内置的中间件更新了,那么开发者就不得不更新整个express
4.x中,express摆脱了对connect的依赖,并且摒弃了除了静态文件模块之外的所有中间件,只包含核心的路由处理(在express中,路由没有被当做中间件)以及其他的代码。
在过去的几年中,express取得了巨大的成功
MEAN(Mongo+Express+Angular+Node)架构成为了不少网站的开发首选,至今依旧非常流行。

Koa
但是Express依旧存在不少的缺点,对于中间件之间的异步流程控制没有提供良好的支持。
随着ES2015标准的落地,express的原班开发人法使用ES2015中的新特性重新打造了新的Web框架—Koa。
Koa的实现与connect更加相似,内部没有提供任何中间件,仅仅作为中间件的调用的脚手架。
Koa的发展同样存在两个阶段Koa1.x 和Koa2,两者之间的区别在于Koa2使用了ES2017中的新特性,这些特性已经在v7.6.0之后的Node版本中提供原生支持。


KOA入门

KOA 1.x 与KOA2
在web开发中,尽管http请求的处理是异步进行的,但我们还是希望能够顺序执行某些操作。
例如在收到http请求时,我们首先先将请求信息写入日志或者数据库,再返回对应的结果,这两个操作往往都是异步进行的,如果我们要顺序完成这两个任务,通常会使用嵌套回调的方式,或者借助一些三方模块进行异步流程控制

为了解决这个问题,Koa诞生了。
在KOA 1.x的版本中,由于当时node还没有完全实现对async/await的支持,因此使用了generator函数来作为异步处理的主要方式,此外,为了实现generator的自动执行,还使用了上一章介绍的co模块作为底层的处理逻辑—它们都是出自同一作者之手。
下面是koa1.x代码的例子

var koa = require('koa');
var app = koa();

// logger

app.use(function *(next){
  var start = new Date;
  yield next;
  var ms = new Date - start;
  console.log('%s %s - %s', this.method, this.url, ms);
});

// response

app.use(function *(){
  this.body = 'Hello World';
});

app.listen(3000);

async/await是ES2017中的一个提案,目的是改进generator函数自动执行的问题,Koa 为此发布了2.0版本,这个版本舍弃了generator函数和模块,完全是使用async函数特性来实现的。

在Node 7.6.0及之后的版本提供了对async函数特性的语言层面的支持,因此要使用koa2,本地的Node环境必须大于7.6.0,否则就要使用babel之类的工具进行转换。

在这个版本的实现中,Koa为我们屏蔽了回调的细节,基本可以认为回调已经“不存在了”。

除此之外,KOA和Express最大的不同之处在于Koa剥离了各种中间件,express似乎也有这种趋势,这种做法的优点是可以让框架变得更加轻量,也有助于开发者自行选择需要的中间件,缺点就是各种中间件都是由第三方开发,质量可能良莠不齐,这点和npm有些类似。
在Koa项目的github页面https://github.com/koajs 中,列出了Koa项目本身和被一些官方整理的中间件列表,开发者也可以自行在github中搜索,查找使用人数较多的一些中间件。
本文主要介绍KOA2的使用


在开始动手之前,我们首先要思考这样几个问题

  • 静态文件怎么处理
  • 路由怎么处理
  • 数据存储怎么办
  • 页面要如何渲染,使用页面模板还是框架

前三个问题属于后端范围,第四个就是前端的工作了,我们将上面的问题分成单独的模块来实现它们。


准备工作
关于使用KOA的准备工作,唯一需要注意的就是Node的版本问题了,这里给出官方推荐的安装方式

nvm install 7
npm i koa
node my-koa-app.js

KOA的Hellworld
按照惯例,从最简单的入门例子来看KOA的使用

const Koa = require('koa');
const app = new Koa();

app.use(ctx => {
  ctx.body = 'Hello World';
});

app.listen(3000);

可以看出,Koa没有使用在Node和express中常用的req和res对象,统一以ctx来代替
关于context对象
在处理http请求时,Node提供了request和response两个对象,Koa把两者封装到了同一个对象中,即context,缩写为ctx
在context中封装了许多方法,大部分都是从原生的request/response 对象使用委托方式得来的,如下表:
from request

• ctx.header
• ctx.headers
• ctx.method
• ctx.method=
• ctx.url
• ctx.url=
• ctx.originalUrl
• ctx.origin
• ctx.href
• ctx.path
• ctx.path=
• ctx.query
• ctx.query=
• ctx.querystring
• ctx.querystring=
• ctx.host
• ctx.hostname
• ctx.fresh
• ctx.stale
• ctx.socket
• ctx.protocol
• ctx.secure
• ctx.ip
• ctx.ips
• ctx.subdomains
• ctx.is()
• ctx.accepts()
• ctx.acceptsEncodings()
• ctx.acceptsCharsets()
• ctx.acceptsLanguages()
• ctx.get()

From response
• ctx.body
• ctx.body=
• ctx.status
• ctx.status=
• ctx.message
• ctx.message=
• ctx.length=
• ctx.length
• ctx.type=
• ctx.type
• ctx.headerSent
• ctx.redirect()
• ctx.attachment()
• ctx.set()
• ctx.append()
• ctx.remove()
• ctx.lastModified=
• ctx.etag=
关于ctx对象是如何获得这些属性和方法的,我们会在koa源码分析一节介绍


使用Koa处理http请求

在这一节里,我们不依赖任何现成的中间件,来介绍Koa是如何处理http请求的。
上面的内容也提到,koa的ctx对象中封装了request以及response对象,那么在处理http请求中,使用ctx对象就可以完成所有的处理
在上面的代码中,我们使用

ctx.body = “Hello World”

上面的代码实际上相当于

res.statusCode = 200;
res.end(“Hello World”);

ctx.body也可以写成ctx.response.body,ctx相当于ctx.request/response的别名

判断http请求类型可以通过ctx.method来进行判断,get请求的参数可以通过ctx.query获取
例如,当用户访问localhost:3000?kindName=Node时,可以使用如下的代码

app.get(‘/’, async (ctx, next) => {
console.log(ctx.query);// { kindName: ‘Node’ }
await next(); });

ctx对象的结构
虽然ctx封装了reqest和response对象的方法,但这并不代表ctx.Request就和原生的request对象完全相同.我们可以试着将原生对象和ctx封装后的对象分别打印出来进行比较

const app = require('koa')();

app.use((ctx,next)=>{
    console.log(ctx.request);
    console.log(ctx.response);
});

app.listen(3001);

ctx.request

{ method: ‘GET’, url: ‘/’, header: { host: ‘localhost:3001’,
connection: ‘keep-alive’,
‘upgrade-insecure-requests’: ‘1’,
‘user-agent’: ‘Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133
Safari/537.36’,
accept: ‘text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,/;q=0.8’,
‘accept-encoding’: ‘gzip, deflate, sdch, br’,
‘accept-language’: ‘zh-CN,zh;q=0.8,en;q=0.6,ja;q=0.4’ } }

ctx.response

{ status: 404, message: ‘Not Found’, header: {}, body: undefined }

可以看出,二者的结构,尤其是ctx.response的结构,和原生的request对象还是有很大区别的,ctx.response对象只是一个单纯的字符串,上面没有注册任何事件,这表示下面的使用方法是错误的

fs.createReadStream(“./1.txt”).pipe(ctx.response);

上面的代码会抛出形如

TypeError: dest.on is not a function

的错误,原因是单纯的字符串无法处理stream对象。


middleware

express中的中间件
在介绍Koa中间件之前,我们暂时先把目光投向express,因为Koa中间件的设计思想大部分来自Connect,而express又是基于connect扩展而来的。
Express本身是由路由和中间件构成的框架,从本质上来说,express的运行就是在不断调用各种中间件。
中间件本质上是接收请求并且做出相应动作的函数,该函数通常接收req和res作为参数,以便对request和response进行操作,在web应用中,中间件是循环运行的,中间件的第三个参数一般写作next,表示下一个中间件。

中间件的功能
由于中间件仍然是一个函数,它可以做到Node代码能做到的任何事情,此外,还包括了修改请求和相应对象,终结请求-相应循环,以及调用下一个中间件等功能,这通常是通过next()方法来实现的。如果在某个个中间件中没有调用next()方法,则表示请求响应-循环到此为止,下一个中间件永远不会被执行。

在Express中使用中间件
Express 应用可使用如下几种中间件:
• 应用级中间件
• 路由级中间件
• 错误处理中间件
• 内置中间件
• 第三方中间件

上面是官网的分类,实际上这几个概念有一些重合之处
从 4.x 版本开始,, Express 已经不再依赖 Connect 了。除了 express.static(负责管理静态资源), Express 以前内置的中间件现在已经全部剥离(路由在express不被看做中间件)

与express不同,Koa没有任何内置的中间件,甚至连路由处理都没有包括在内,所有中间件都要通过第三方模块来实现,因此我们说比起express来,更像是Connect。


中间件的调用
无论是express还是koa,中间件的调用都是通过next()函数来执行的,当我们调用app.use()方法时,实际上在内部形成了一个中间件数组,next()方法负责调用数组中的下一个中间件。如果在一个中间件中没有调用next()方法,那么中间件的调用会中断,后续的中间件都不会被执行。

中间件的顺序执行
在Web开发中,我们通常希望能够顺序执行一些操作,例如当收到http请求后,首先向日志系统中写入数据,然后再进行路由处理,最后再进行数据库查询,这些应该是同步进行的。在Express和Koa中,表现为顺序调用某些中间件。在Express中,要实现中间件的顺序调用并不容易。

下面的代码定义了两个express中间件,和之前不同之处在于第二个中间件中调用了process.nextTrick(),表示这是一个异步操作

var app = require(‘express’)(); 
app.use(function(req,res, next){
     next();
    console.log("after next")
});
app.use(function(req,res,next){
    process.nextTick(function(){
        console.log("before next");
        next();
    });
});

得到的运行结果是

after next
before next

这是因为在调用第一个next()后,第二个中间件内的process.nextTick由于是异步调用的,因此马上返回到第一个中间件,继续输出after next,然后中间件二的回调返回结果,输出before next

Koa中的中间件
Koa和express中间件最大的不同之处,在于Koa使用了async/await方法,可以方便地实现中间件的顺序调用,而express中不借助第三方模块的话,无法保证这一点。
借助async/await方法,事情变得简单了

var Koa = require("koa");
var app = new Koa();
app.use( async (ctx, next) =>{
    await next();
    console.log("after next")
});
app.use( async (ctx, next) =>{
    process.nextTick(function(){
        console.log("before next");
        next();
    });
});
app.listen(3000);

使用Koa改写之后,代码输出:

before next after next

从本质上看,express和Koa其实区别并不大,所谓的async/await也不过是语法糖而已,但就是小小的语法糖简化了整个开发流程和代码量,仅这一点就够了。


本节的内容到此结束,下一节讲述路由以及静态文件处理的实现

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用Koa2进行Web开发(一) 的相关文章

  • 将实体投射到 dto

    只是想知道将 NestJS 实体对象转换为 DTO 的最佳方法 可以说我有以下内容 import IsString IsNumber IsBoolean from class validator import Exclude from cl
  • 在 Node.js 中每 4 小时安排一次任务

    如何使用 Node js 中的 node schedule 安排任务在 4 小时后运行 目前我的代码如下 但它没有按预期响应 var schedule require node schedule var task schedule sche
  • 使用单个“proxyServer”将 Websocket 代理到多个目标

    我正在开发一个nodeJS websocket代理服务器 用例是当 websocket 请求到来时 我将检查其凭据 添加新标头 然后根据其组 来自用户 ID 将 websocket 连接重定向到其目标 webscoket 服务器 我发现大多
  • 将文件从一个文件夹移动到 s3 中的另一个文件夹

    首先 我尝试将文件复制到其他文件夹中 但无法删除它 仅当文件复制到目标文件夹时 如何才能删除该文件 const s3Params Bucket bucket CopySource bucket objectkey Key processed
  • 将 Docker 与具有 Node-gyp 依赖项的 Nodejs 一起使用

    我计划使用 Docker 部署 node js 应用程序 该应用程序有几个需要node gyp的依赖项 Node gyp 根据交付平台上的编译库构建这些模块 例如 canvas lwip qrcode 根据我的经验 这些构建可能高度依赖于操
  • Nodejs 异步 Promise 队列

    我需要使用速率受限的 API 例如 我一秒钟只能进行 10 个 API 调用 因此我需要等待当前秒结束才能进行另一个 API 调用 为了实现这一目标 我想创建一个可以自行管理的异步队列 它的主要功能是让我向队列添加一个新的 Promise
  • axios 如何将 blob 与 arraybuffer 作为响应类型处理?

    我正在下载一个 zip 文件axios https www npmjs com package axios 为了进一步处理 我需要获取已下载的 原始 数据 据我所知 Javascript 有两种类型 Blob 和 Arraybuffers
  • 获取 Promise 的值并分配给变量

    utility fetchInfo 返回一个 Promise 对象 我需要能够获取此 Promise 对象的值 并将其值分配给一个变量 以便稍后在我的代码中使用 此刻 我可以愉快地打印出result到控制台 但我需要能够将此值分配给myVa
  • Node.js 工作线程中的 I/O 性能

    下面是一个工作线程示例 在本地计算机上同步 I O 大约需要 600 毫秒 const fs require fs const isMainThread Worker parentPort workerData require worker
  • NodeJS 找不到模块“grpc”

    我尝试在我的树莓派 3 上运行 JS 脚本 但我不断返回一个似乎是 grpc 模块的问题 我已经尝试重新安装和重建 npm 以下https github com firebase firebase tools issues 442 http
  • 使用 React-navigation 的 React Native 中的模态窗口

    我在用react navigation在 React Native 中 我想在启动时确定用户是否已登录 如果他 她已经登录 我想打开一个模式窗口 全屏 如何最好地做到这一点 我在反应导航文档中找不到有条件显示屏幕的任何内容 看 你需要改变m
  • 如何使用expressjs发送多个文件?

    我希望能够发送许多文件 如果可能的话 发送整个目录 以便我可以在从 html 文件调用的其他 js 文件中访问它 const app require express const http require http Server app co
  • PassportJS - 自定义回调并将 Session 设置为 false

    是否可以使用自定义回调并禁用会话 在文档中 它显示了如何禁用会话和自定义回调 但如何组合它们 app get login function req res next passport authenticate local function
  • CoreMongooseArray 到普通数组

    我正在从一个架构中选出 2 个元素 并希望在另一个架构中进行更新 为此 我使用切片方法将数组中的前 2 个元素列入候选名单 但我越来越 CoreMongooseArray 元素1 元素2 而不是 元素1 元素2 如何删除 CoreMongo
  • WebPack 源映射令人困惑(重复文件)

    我决定在我今天正在启动的一个新项目上尝试 WebPack 并且我从源映射中得到了非常奇怪的行为 我在文档中找不到任何相关信息 在浏览 StackOverflow 时也找不到其他人遇到此问题 我目前正在查看由以下公司制作的 HelloWorl
  • 使用 Bluebird.js 和 Twitter 流的 Promise 和流

    我对 Promises 和 Node 非常陌生 并且对在流中使用 Promise 感到好奇 我可以承诺直播吗 使用 Bluebirdjs 和 Twit 模块 我有以下内容 var Twit require twit var Promise
  • MongoDB 和 Mongoose 访问一个数据库,同时针对另一个数据库进行身份验证(NodeJS、Mongoose)

    我有几个数据库 不想为每个数据库创建单独的用户帐户 MongoDB 支持使用另一个数据库中定义的帐户来验证对数据库的访问的概念 但语法示例很难获得 当我终于弄清楚时 我正准备提出一个问题 如果它对其他人有帮助 就放在这里 这是 mongod
  • Express.js - 监听关闭

    我有一个使用 Express 的 Node js 应用程序 在该应用程序中 我有一个如下所示的块 const app require app const port process env PORT 8080 const server app
  • JavaScript - 类根据条件扩展

    事情是这样的 我有一个名为 A 的主课 我希望这个班级能够扩展 B 级 class A extends B 但事实上 我希望 B 类在特定条件下扩展 C D 或 E class B extends B1 or class B extends
  • 已安装全局 NPM 包但未找到命令

    我已经全局安装了两个 npm 包 下载 https www npmjs com package download and 谜虚拟盒 https www npmjs com package enigmavirtualbox通过命令行 npm

随机推荐

  • Llama2下载流程与报错:download.sh: [[: not found Downloading LICENSE and Acceptable Usage Policy..

    最近Meta的新模型LlamaV2可谓是火出圈了 第一时间我也尝试下载了权重 下载Llama2需要首先取得许可 不过没有门槛 秒批 https ai meta com resources models and libraries llama
  • C++11的 thread多线程

    一 C 11的多线程类thread C 11之前 C 库中没有提供和线程相关的类或者接口 因此在编写多线程程序时 Windows上需要调用CreateThread创建线程 Linux下需要调用clone或者pthread create来创建
  • RocketMQ系列之集群搭建

    前言 上节我们对RocketMQ 以下简称RMQ 有了一些基本的认识 大致知道了 什么是RMQ以及他能做什么 今天我们来讲讲如何搭建RMQ 与其说搭建RMQ不如说是搭建RMQ集群 为什么这么说呢 看完这篇文章自然就懂了 RMQ几个重要角色
  • 蓝桥杯2022年第十三届决赛真题-小球称重

    目录 题目描述 输入格式 输出格式 样例输入 样例输出 提示 原题链接 代码思路 题目描述 小蓝有 N 个小球 编号 1 至 N 其中 N 1 是正品 重量相同 有 1 个是次品 重量比正品轻 为了找出次品 小蓝已经用天平进行了 M 次称重
  • 0-1背包问题使用回溯法

    对于0 1 背包问题可以用动态规划算法解决 这里先不说这种方法 只介绍回溯法 0 1背包问题的回溯法解决的解空间是子集树 下面给出最简洁的代码 比较方便理解呢 include
  • SSD固态硬盘的结构和基本工作原理概述

    我们都知道 早期的电脑CPU是可以直接从硬盘上面读取数据进行处理的 随着科技的进步 时代的发展 计算机硬件的发展速度也是极其迅猛 CPU主频的不断提升 从单核到双核 再到多核 CPU的处理速度越来越快 而硬盘的的读写速度已经远远跟不上CPU
  • Elasticsearch报错ValueError: Either ‘hosts‘ or ‘cloud_id‘ must be specified

    这个错误是由于在初始化 Elasticsearch 客户端时未指定有效的主机地址 hosts 或 Cloud ID cloud id 而引起的 Elasticsearch 客户端需要知道连接的 Elasticsearch 实例的位置才能正常
  • MMYOLO框架标注、训练、测试全流程(补充篇)

    前言 MMYOLO框架是一个基于PyTorch和MMDetection的YOLO系列算法开源工具箱 MMYOLO定位为YOLO系列热门开源库以及工业应用核心库 MMYOLO框架Github项目地址 支持的任务 目标检测 旋转目标检测 支持的
  • 【blender建模功能】03 倒角工具

    blender 03 倒角工具 基操 宽度类型 其他参数 倒角问题 顶点倒角 1 基础操作 2 宽度类型 3 其他参数 3 1 材质编号 3 2 平滑 3 2 1 自动光滑 3 2 2 硬化法线 3 3 钳制重叠 3 4 外衔接 内衔接 3
  • UE4联网2——视角同步

    在做完子弹的同步后发现和客户端和服务器的玩家的仰角是不同步的 所以在角色代码中加入tick函数更新玩家的仰角pitch 这里我们需要用到一个变量RemoteViewPitch 这是在pawn中定义的已经复制的公有变量 rpc 值得注意的是它
  • 忽略大小写的字符串比较

    问题描述 一般我们用strcmp可比较两个字符串的大小 比较方法为对两个字符串从前往后逐个字符相比较 按 ASCII 码值大小比较 直到出现不同的字符或遇到 0 为止 如果全部字符都相同 则认为相同 如果出现不相同的字符 则以第一个不相同的
  • vue3引用ElementPlus出错|如何在vue中引用TypeScript

    具体错误 直接套用elementplus官方文档里的模版 报错 Module parse failed Unexpected token You may need an additional loader to handle the res
  • 运放噪声如何计算?

    一 噪声 运放的噪声分为 1 电压噪声en v 2 电流噪声在电阻Rs和R1 R2上产生的等效噪声en i 3 电阻的热噪声enr 总输入噪声计算公式 en in sqrt env 2 eni 2 enr 2 总输出噪声计算公式 en ou
  • [第七届蓝帽杯全国大学生网络安全技能大赛 蓝帽杯 2023]——Web方向部分题 详细Writeup

    Web LovePHP 你真的熟悉PHP吗 源码如下
  • 【C++】C++入门

    目录 一 C 关键字 二 命名空间 2 1命名空间的定义 2 2命名空间的使用 2 2 1加命名空间名称和作用域限定符 2 2 2使用using 将命名空间中某个成员引入 2 2 3使用using namespace将命名空间引入 三 C
  • 【KnowledgeBase】CLIP多模态代码试玩

    文章目录 前言 一 CLIP整体流程简述 二 代码试玩 参考 前言 多模态CLIP的推理部分代码简单试玩一下 致敬大佬的CLIP 论文链接 Learning Transferable Visual Models From Natural L
  • JS中的“&&”与“&”和“

    在JavaScript中 和 是逻辑运算符 和 是位运算符 四个运算符主要区别是运算方法不一样 1 JavaScript中的位运算符 运算方法 两个数值的个位分别相与 同时为1才得1 只要一个为0就为0 举个例子 31 2 结果为2 理由
  • python-报错

    报错 异常名称 描述 BaseException 所有异常的基类 SystemExit 解释器请求退出 KeyboardInterrupt 用户中断执行 通常是输入 C Exception 常规错误的基类 StopIteration 迭代器
  • JS 统计字符

    var str id content value replace r n g n var length t str length
  • 使用Koa2进行Web开发(一)

    这篇文章是我正在进行写作的 新时期的Node js入门 的一部分 Connect Express与Koa 为了更好地理解后面的内容 首先需要梳理一下Node中Web框架的发展历程 Connect 在connect官方网站提供的定义是 Con