Express 路径转正则 path-to-regexp

一、开始之前

在开始之前,我们要先聊一聊 restful 规范,也就是如何根据 url 从服务器获取资源。

1.1)以商品获取为例

 

sh

复制代码

GET /api/products

我们要通过 HTTP 协议的 GET 方法获取 url 在 /api 下的 products 列表。这很简单程序很容易识别固定的 url。

1.2)获取具体的内容

 

sh

复制代码

GET /api/product/1 GET /api/product/2 GET /api/product/3 GET /api/product/9999

我们看到使用枚举的方式也可以轻松的完成,但是这些枚举的路径在程序中都是可以重复的,可以抽象的。

我们可以认为:/product/id 中 id 是一个传递的参数,是动态的。我们尝试将这个动态的用别的形式展示

  • /product/:id
  • /product/{id}
  • /product/$id
  • ...

等等不同的表现形式。其中 express 就选择了第一种方式进行解析动态路径。

关联了 restful 规范和路径动态表现形式,下面就需要一些方式来解析路径,path-to-regexp 就一个应用于 express 中的路径解释库。下面我们就开始进入它讲解。

二、哪些库在使用 path-to-regexp

  • express
  • koa
  • vue-router2
  • nestjs

2.1)express 路径的写法

经常写 expressjs 或者类似框架小伙伴,可能都会写如下的代码:

 

sh

复制代码

app.get('/users/:id', (req, res) => { res.send(`获取用户 ${req.params.id}`); });

/users/:id 路径的写法,代表 id 是一个 动态参数,可以匹配不同的 id,来经过这个路由函数进行处理。那么 express 如何如何在内部关联上 path-to-regexp 的呢?

2.2)express 在 Layer 使用使用

express 中 path-to-regexp 也只在 一个 地方用到了 Layer 类中。

 

js

复制代码

var pathRegexp = require('path-to-regexp'); function Layer(path, options, fn) { if (!(this instanceof Layer)) { return new Layer(path, options, fn); } // *** this.regexp = pathRegexp(path, this.keys = [], opts); // *** }

这样就简单了。path-to-regexp 与 layer 进行关联。所有使用实例化 layer 位置就需要解析 path 为 regexp。

2.3)nestjs 路径的写法

 

ts

复制代码

import { Controller, Get, Param } from '@nestjs/common'; @Controller('cats') export class CatsController { @Get(':id') findOne(@Param('id') id: string) { return `This action returns a #${id} cat`; } }

三、path-to-regexp API 设计

使用 Object.keys 快速获取 path-to-regexp 的 api。

 

js

复制代码

import * as ptr from 'path-to-regexp' const keys = Object.keys(ptr) console.log(keys) [ '__esModule', 'compile', 'default', 'match', 'parse', 'pathToRegexp', 'regexpToFunction', 'tokensToFunction', 'tokensToRegexp' ]

以下是 api 整体的输出情况:

项目说明
__esModule表示是一个 ES 模块
compile路径字符串编译为生成 URL函数
default默认导出的模块或函数
match生成匹配 URL 的函数 matchFn
parse路径字符串解析为令牌 数组
pathToRegexp路径字符串转换为正则表达式,并提取参数
regexpToFunction正则表达式转换为匹配 URL函数
tokensToFunction令牌数组转换为生成URL函数
tokensToRegexp令牌数组转换为正则表达式

四、pathToRegexp

pathToRegexp('/user/:id') -> regexpObject

pathToRegexp 迅速将 url 转换成正则。

 

js

复制代码

import { pathToRegexp } from 'path-to-regexp' const keys = []; const regexp = pathToRegexp('/user/:id', keys); console.log(regexp); // /^\/user\/([^\/]+?)\/?$/i console.log(keys);

有了正则就可以做其他的匹配事情了:

 

js

复制代码

regexp.exec('/user/23')

五、match 匹配函数

match('/user/:id') -> matchFn -> matchObject/false

match 函数用于创建一个新的匹配函数 matchFn,以下是一个获取用户 id 的例子:

 

sh

复制代码

const matchUserFn = match("/user/:id") const result = matchUserFn("/user/123") const userId = result.params.id // 123 const result = matchFn('/user/123/ad?d=123'); // false

从抽象的地址到具体的地址,本质就是 :id 与 params.id 的一个映射的过程。能够方便的以 js 对象的形式获取数据。当然 match 还是传入 options,对正则表达式进行约束,这里就不再赘述了。如果把 match 立即为正面的匹配的话,那么它的反面是如何呈现的呢?

六、compile

compile('/user/:id') -> toPath -> url

将对象转换 path, 与 match 相反 compile 是已知 id 需要将 id 转换成路径:

 

js

复制代码

const toPath = compile("/user/:id") const user = { id: 123 } const userPath = toPath(user) // /user/123

七、parse

parse('/user/:id') -> tokens

7.1)词法对象

path-to-regexp 实现 lexer 词法分析,使用 parse 对 url 进行词法分析产生 tokens,词法对象:

 

ts

复制代码

{ type: "MODIFIER" | "ESCAPED_CHAR" | "OPEN" || "CLOSE" || "NAME" || "PATTERN" || "CHAR" || "END", index, value, }

7.2)token 对象

parse 解析完毕词法之后,在此数组的基础上,将其解析为 tokens 数组,下面是 token 数组的大致数据形式:

 

js

复制代码

{ name: name || key++, prefix: prefix, suffix: "", pattern: pattern || defaultPattern, modifier: tryConsume("MODIFIER") || "", }

7.3)示例

 

js

复制代码

import { parse } from 'path-to-regexp' const tokens = parse('/user/:id'); console.log(tokens); /** * [ '/user', { name: 'id', prefix: '/', suffix: '', pattern: '[^\\/#\\?]+?', modifier: '' } ] */

八、tokenTo 相关转换函数

前面已经提到了词法解析与 tokens 的解析 path-to-regexp 也提供了两个方法用于奖励 token 与 url 和正则的转换关系

8.1)tokensToFunction token 转换成函数

parse('/user/:id') -> tokens -> tokensToFunction({ id: 123}) -> path_url

 

ts

复制代码

import { parse, tokensToFunction } from 'path-to-regexp' const tokens = parse('/user/:id'); const toPath = tokensToFunction(tokens); console.log(toPath({ id: 123 })); // /user/123

8.2)tokensToRegexp

parse('/user/:id') -> tokens -> tokensToRegexp -> regexp

 

ts

复制代码

import { parse, tokensToRegexp } from 'path-to-regexp'; const tokens = parse('/user/:id'); const keys = []; const regexp = tokensToRegexp(tokens, keys); console.log(regexp); // /^\/user\/([^\/]+?)\/?$/i console.log(keys); // [ { name: 'id', prefix: '/', ... }]

九、与 node.js http 模块

 

ts

复制代码

const http = require("http"); const { pathToRegexp } = require("path-to-regexp"); // 定义路由和处理函数 const routes = [ { method: "GET", path: "/", handler: (req, res) => { res.writeHead(200, { "Content-Type": "text/plain" }); res.end("Hello World!"); }, }, { method: "GET", path: "/user/:id", handler: (req, res, params) => { res.writeHead(200, { "Content-Type": "text/plain" }); res.end(`User ID is ${req.params.id}`); }, }, { method: "POST", path: "/submit", handler: (req, res) => { res.writeHead(200, { "Content-Type": "text/plain" }); res.end("Form submitted!"); }, }, ]; function matchRoute(req) { const route = routes.find((route) => { const keys = []; const re = pathToRegexp(route.path, keys); const result = re.exec(req.url); if (result && req.method === route.method) { req.params = {}; for (let i = 1; i < result.length; i++) { req.params[keys[i - 1].name] = result[i]; } return true; } return false; }); return route; } // 创建服务器 const server = http.createServer((req, res) => { // 找到匹配的路由 const route = matchRoute(req) // 执行路由处理函数 if (route) { route.handler(req, res); } else { res.writeHead(404, { "Content-Type": "text/plain" }); res.end("Not Found"); } }); // 启动服务器 server.listen(3000, () => { console.log("Server is running on port 3000"); });

大致分为三个部分:

  1. 定义路由
  2. 路由匹配与处理
  3. 启动服务

这是一个简单的路由匹配,但是也能够帮助我们理解 http 中路由匹配的基本用法。

十、如何构建

  • 基于 typescript 编写
  • 使用 @borderless/ts-scripts 进行构建
  • 使用 size-limit 进行尺寸大小测试

十一、其他语言版本

语言使用
gogo get -u github.com/soongo/path-to-regexp
rustcargo add path2regex
path-to-regexp-rust
pythonpython-repath

十二、小结

本文从 restful 获取服务器资源的 url 开始到,express 中常用获取用户 id 的资源到 path-to-regexp 的api 设计使用方式,使用原生 Node.js + path-to-regexp 实现一个简单的路由匹配。最后希望能够帮助到大家。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值