Третья статья посвящена более важному промежуточному ПО в экосистеме koa:koa-router
первый раз:чтение исходного кода koa-0
Вторая статья:чтение исходного кода koa-1-koa и koa-compose
что такое коа-маршрутизатор
Во-первых, поскольку koa — это платформа для управления промежуточным ПО, регистрация промежуточного ПО используетuse
выполнить.
Независимо от того, какой запрос, все промежуточное ПО будет выполнено один раз (если не на полпути).
Таким образом, разработчики будут очень обеспокоены: если мы хотим выполнять маршрутизацию, как мы должны писать логику?
app.use(ctx => {
switch (ctx.url) {
case '/':
case '/index':
ctx.body = 'index'
break
case 'list':
ctx.body = 'list'
break
default:
ctx.body = 'not found'
}
})
Согласитесь, это простой метод, но он точно не подходит для больших проектов, где через один проходят десятки интерфейсов.switch
Это слишком сложно контролировать.
Не говоря уже о том, что запросы могут поддерживать толькоget
илиpost
, и этот метод не очень хорошо поддерживает запросы с параметрами в URL/info/:uid
.
существуетexpress
Нет такой проблемы вget
,post
и т. д. сMETHOD
Одноименная функция используется для регистрации обратного вызова:
express
const express = require('express')
const app = express()
app.get('/', function (req, res) {
res.send('hi there.')
})
ноkoa
Было сделано много упрощений, и большая часть логики была разделена, чтобы существовать как независимое промежуточное программное обеспечение.
так привести к многоexpress
Проект перенесен вkoa
Когда вам нужно установить дополнительное ПО промежуточного слоя,koa-router
Надо сказать, что это самый распространенный из них.
так вkoa
требует дополнительной установкиkoa-router
для реализации аналогичной функции маршрутизации:
koa
const Koa = require('koa')
const Router = require('koa-router')
const app = new Koa()
const router = new Router()
router.get('/', async ctx => {
ctx.body = 'hi there.'
})
app.use(router.routes())
.use(router.allowedMethods())
Кажется, кода действительно немного больше, ведь много логики передается из фреймворка в middleware для обработки.
Его можно рассматривать как то, что выбрано для поддержания краткой структуры коа.
Логика koa-router действительно сложнее логики koa.Вы можете представить koa как рынок, а koa-router это один из прилавков
koa нужно только обеспечить стабильную работу рынка, а реальная сделка с клиентами - это действительно koa-маршрутизатор, который устанавливает киоск внутри
Общая структура koa-router
koa-router
Структура не очень сложная, и она разбита на два файла:
.
├── layer.js
└── router.ja
layer
В основном для инкапсуляции некоторой информации основное дорожное полотноrouter
поставка:
File | Description |
---|---|
layer |
Хранение информации: путь, МЕТОД, соответствующий обычному пути, соответствует параметрам пути, путь, соответствующий промежуточному программному обеспечению |
router |
Основная логика: выставить функции для регистрации маршрутов, предоставить промежуточное ПО для обработки маршрутов, проверить запрошенный URL и вызвать обработку маршрута на соответствующем уровне |
Работающий процесс koa-router
Возьмите базовый пример, приведенный выше, чтобы проиллюстрироватьkoa-router
Что представляет собой процесс исполнения:
const router = new Router() // 实例化一个Router对象
// 注册一个路由的监听
router.get('/', async ctx => {
ctx.body = 'hi there.'
})
app
.use(router.routes()) // 将该Router对象的中间件注册到Koa实例上,后续请求的主要处理逻辑
.use(router.allowedMethods()) // 添加针对OPTIONS的响应处理,以及一些METHOD不支持的处理
Некоторые вещи при создании экземпляра
Первый вkoa-router
При создании экземпляра можно передать параметр элемента конфигурации в качестве информации конфигурации инициализации.
Однако этот элемент конфигурацииreadme
просто описывается как:
Param | Type | Description |
---|---|---|
[opts] |
Object |
|
[opts.prefix] |
String |
префиксные пути маршрутизатора |
скажите нам, что мы можем добавитьRouter
Префикс при регистрации, то есть, если он разделен по модульности, не нужно добавлять огромный префикс в начало каждого сопоставления путей:
const Router = require('koa-router')
const router = new Router({
prefix: '/my/awesome/prefix'
})
router.get('/index', ctx => { ctx.body = 'pong!' })
// curl /my/awesome/prefix/index => pong!
P.S. Но имейте в виду, что еслиprefix
к/
В конце регистрации маршрута можно опустить префикс/
, иначе появится/
повторяющийся случай
создавать экземплярRouter
код, когда:
function Router(opts) {
if (!(this instanceof Router)) {
return new Router(opts)
}
this.opts = opts || {}
this.methods = this.opts.methods || [
'HEAD',
'OPTIONS',
'GET',
'PUT',
'PATCH',
'POST',
'DELETE'
]
this.params = {}
this.stack = []
}
только один видимыйmethods
Назначение , но после просмотра другого исходного кода я обнаружил, что в дополнение кprefix
Есть также некоторые параметры, которые передаются во время создания экземпляра, но неясно, почему документация не упоминает об этом:
Param | Type | Default | Description |
---|---|---|---|
sensitive |
Boolean |
false |
Строго ли соответствовать регистру |
strict |
Boolean |
false |
Если установленоfalse затем соответствует следующему/ не является обязательным |
methods |
Array[String] |
['HEAD','OPTIONS','GET','PUT','PATCH','POST','DELETE'] |
Установите МЕТОД, который может поддерживать маршрут |
routerPath |
String | null |
sensitive
если установленоsensitive
, маршрут будет отслеживаться по более строгим правилам сопоставления, регистр в URL не будет игнорироваться, и он будет сопоставляться точно по регистрации:
const Router = require('koa-router')
const router = new Router({
sensitive: true
})
router.get('/index', ctx => { ctx.body = 'pong!' })
// curl /index => pong!
// curl /Index => 404
strict
strict
а такжеsensitive
Похожий по функциям, он также используется для установки сопоставления, чтобы сделать путь более строгим.По умолчанию путь в конце/
Необязателен, если этот параметр включен, если хвост не добавляется при регистрации маршрута/
, соответствующий маршрут не должен добавляться./
конец:
const Router = require('koa-router')
const router = new Router({
strict: true
})
router.get('/index', ctx => { ctx.body = 'pong!' })
// curl /index => pong!
// curl /Index => pong!
// curl /index/ => 404
methods
methods
Значение существования элементов конфигурации заключается в том, что если у нас есть интерфейс, который необходимо поддерживать одновременноGET
а такжеPOST
,router.get
,router.post
Такое письмо обязательно будет некрасивым.
Таким образом, мы могли бы подумать об использованииrouter.all
упростить вещи:
const Router = require('koa-router')
const router = new Router()
router.all('/ping', ctx => { ctx.body = 'pong!' })
// curl -X GET /index => pong!
// curl -X POST /index => pong!
Это просто идеально, он может легко удовлетворить наши потребности, но если мы поэкспериментируем с каким-то другимmethods
Позже произошли неприятные вещи:
> curl -X DELETE /index => pong!
> curl -X PUT /index => pong!
Это явно не то, на что мы рассчитывали, поэтому в данном случае, исходя из текущегоkoa-router
Для достижения того, что мы хотим, необходимы следующие модификации:
const Koa = require('koa')
const Router = require('router')
const app = new Koa()
// 修改处1
const methods = ['GET', 'POST']
const router = new Router({
methods
})
// 修改处2
router.all('/', async (ctx, next) => {
// 理想情况下,这些判断应该交由中间件来完成
if (!~methods.indexOf(ctx.method)) {
return await next()
}
ctx.body = 'pong!'
})
Эти две модификации могут реализовать ожидаемую функцию:
> curl -X GET /index => pong!
> curl -X POST /index => pong!
> curl -X DELETE /index => Not Implemented
> curl -X PUT /index => Not Implemented
я лично считаю что этоallowedMethods
Логичная проблема реализации, но возможно я не дошел до автора,allowedMethods
Некоторые из наиболее важных исходных кодов:
Router.prototype.allowedMethods = function (options) {
options = options || {}
let implemented = this.methods
return function allowedMethods(ctx, next) {
return next().then(function() {
let allowed = {}
// 如果进行了ctx.body赋值,必然不会执行后续的逻辑
// 所以就需要我们自己在中间件中进行判断
if (!ctx.status || ctx.status === 404) {
if (!~implemented.indexOf(ctx.method)) {
if (options.throw) {
let notImplementedThrowable
if (typeof options.notImplemented === 'function') {
notImplementedThrowable = options.notImplemented() // set whatever the user returns from their function
} else {
notImplementedThrowable = new HttpError.NotImplemented()
}
throw notImplementedThrowable
} else {
ctx.status = 501
ctx.set('Allow', allowedArr.join(', '))
}
} else if (allowedArr.length) {
// ...
}
}
})
}
}
первый,allowedMethods
Он существует как промежуточное программное обеспечение поста, потому что он вызывается первым в возвращаемой функции.next
, с последующимMETHOD
, и одним из следствий этого является то, что если мы сделаем что-то подобное в обратном вызове маршрутаctx.body = XXX
, фактически изменит запросstatus
значение, чтобы оно не стало404
и не может вызвать правильноMETHOD
Проверьте логику.
нужен правильный триггерMETHOD
Логика, надо вручную судить в мониторинге роутингаctx.method
Это то, что мы хотим, тогда пропустим выполнение текущего промежуточного программного обеспечения.
И шаг этого суждения фактически такой же, какallowedMethods
в промежуточном программном обеспечении!~implemented.indexOf(ctx.method)
Логика полностью повторяющаяся и не очень понятнаяkoa-router
Почему ты делаешь это.
Конечно,allowedMethods
Оно не может существовать как предварительное промежуточное ПО, потому чтоKoa
может висеть в несколькихRouter
,Router
Конфигурации могут различаться междуRouter
оба и текущиеRouter
обрабатываемыйMETHOD
это то же самое.
Итак, личное ощущениеmethods
Существование параметров не имеет большого смысла. .
routerPath
существование этого параметра. . Такое ощущение, что это может привести к очень странным ситуациям.
Это означает, что после регистрации промежуточного ПОrouter.routes()
Операция делается:
Router.prototype.routes = Router.prototype.middleware = function () {
let router = this
let dispatch = function dispatch(ctx, next) {
let path = router.opts.routerPath || ctx.routerPath || ctx.path
let matched = router.match(path, ctx.method)
// 如果匹配到则执行对应的中间件
// 执行后续操作
}
return dispatch
}
потому что мы на самом делеkoa
Регистрация — это такое промежуточное ПО, которое будет выполняться каждый раз при отправке запроса.dispatch
, пока вdispatch
определить, следует ли ударить по определенномуrouter
, будет использоваться этот элемент конфигурации, такое выражение:router.opts.routerPath || ctx.routerPath || ctx.path
,router
ПредставительRouter
экземпляр, то есть, если мы создаемRouter
, если заполненоrouterPath
, что приведет к тому, что любой запрос будет иметь приоритетrouterPath
как проверка маршрута:
const router = new Router({
routerPath: '/index'
})
router.all('/index', async (ctx, next) => {
ctx.body = 'pong!'
})
app.use(router.routes())
app.listen(8888, _ => console.log('server run as http://127.0.0.1:8888'))
Если такой код есть, какой бы URL ни запрашивался, он будет считаться/index
соответствовать:
> curl http://127.0.0.1:8888
pong!
> curl http://127.0.0.1:8888/index
pong!
> curl http://127.0.0.1:8888/whatever/path
pong!
Умелое использование routerPath для реализации функции переадресации
Точно так же этот оператор короткого замыкания имеет три выражения, второеctx
является контекстом текущего запроса, то есть если у нас естьroutes
Выполняемому промежуточному программному обеспечению также могут быть присвоены значения для изменения используемой оценки маршрутизации.URL
:
const router = new Router()
router.all('/index', async (ctx, next) => {
ctx.body = 'pong!'
})
app.use((ctx, next) => {
ctx.routerPath = '/index' // 手动改变routerPath
next()
})
app.use(router.routes())
app.listen(8888, _ => console.log('server run as http://127.0.0.1:8888'))
Такой код также может достичь того же эффекта.
прошел в инстанцированииrouterPath
Непредсказуемое, но изменение промежуточного программного обеспеченияrouterPath
Найти подходящую сцену по-прежнему можно.Просто это можно понимать как реализацию переадресации.Процесс переадресации невидим для клиента.С точки зрения клиента исходный URL по-прежнему доступен, но в промежуточном программном обеспечении Изменитьctx.routerPath
Легко сопоставить маршрут туда, куда мы хотим его переслать.
// 老版本的登录逻辑处理
router.post('/login', ctx => {
ctx.body = 'old login logic!'
})
// 新版本的登录处理逻辑
router.post('/login-v2', ctx => {
ctx.body = 'new login logic!'
})
app.use((ctx, next) => {
if (ctx.path === '/login') { // 匹配到旧版请求,转发到新版
ctx.routerPath = '/login-v2' // 手动改变routerPath
}
next()
})
app.use(router.routes())
Это обеспечивает простую переадресацию:
> curl -X POST http://127.0.0.1:8888/login
new login logic!
Слушатели для зарегистрированных маршрутов
Все вышесказанное касается инстанцированияRouter
Некоторые операции в то время, давайте поговорим о наиболее часто используемых операциях, связанных с маршрутизацией регистрации, наиболее знакомыми должны бытьrouter.get
,router.post
Эти действуют.
Но на самом деле это всего лишь ярлыки, внутренне вызывающиеRouter
изregister
метод:
Router.prototype.register = function (path, methods, middleware, opts) {
opts = opts || {}
let router = this
let stack = this.stack
// support array of paths
if (Array.isArray(path)) {
path.forEach(function (p) {
router.register.call(router, p, methods, middleware, opts)
})
return this
}
// create route
let route = new Layer(path, methods, middleware, {
end: opts.end === false ? opts.end : true,
name: opts.name,
sensitive: opts.sensitive || this.opts.sensitive || false,
strict: opts.strict || this.opts.strict || false,
prefix: opts.prefix || this.opts.prefix || '',
ignoreCaptures: opts.ignoreCaptures
})
if (this.opts.prefix) {
route.setPrefix(this.opts.prefix)
}
// add parameter middleware
Object.keys(this.params).forEach(function (param) {
route.param(param, this.params[param])
}, this)
stack.push(route)
return route
}
В комментариях метод помечен как приватный, но некоторые его параметры не отражены в разных местах кода.Призрак знает, почему эти параметры сохранены, но раз он существует, то вам нужно знать, что он делает
Это основной метод мониторинга маршрутизации.Сигнатура функции примерно следующая:
Param | Type | Default | Description |
---|---|---|---|
path |
String /Array[String]
|
- | Один или несколько путей |
methods |
Array[String] |
- | Какие маршруты должен прослушивать этот маршрут?METHOD
|
middleware |
Function /Array[Function]
|
- | Массив промежуточного программного обеспечения, состоящий из функций, которые направляют фактическую вызываемую функцию обратного вызова. |
opts |
Object |
{} |
Некоторые параметры конфигурации при регистрации маршрутов, упомянутые вышеstrict ,sensitive а такжеprefix проявляется здесь |
Как видите, функция примерно реализует этот процесс:
- экзамен
path
Является ли это массивом, если да, пройдитеitem
позвонить самому себе - создать экземпляр
Layer
объект, установите некоторые параметры инициализации - Установить обработку промежуточного ПО для определенных параметров (если есть)
- Поместите созданный объект в
stack
средний накопитель
Итак, прежде чем вводить эти параметры, кратко опишитеLayer
Конструктор необходим:
function Layer(path, methods, middleware, opts) {
this.opts = opts || {}
this.name = this.opts.name || null
this.methods = []
this.paramNames = []
this.stack = Array.isArray(middleware) ? middleware : [middleware]
methods.forEach(function(method) {
var l = this.methods.push(method.toUpperCase());
if (this.methods[l-1] === 'GET') {
this.methods.unshift('HEAD')
}
}, this)
// ensure middleware is a function
this.stack.forEach(function(fn) {
var type = (typeof fn)
if (type !== 'function') {
throw new Error(
methods.toString() + " `" + (this.opts.name || path) +"`: `middleware` "
+ "must be a function, not `" + type + "`"
)
}
}, this)
this.path = path
this.regexp = pathToRegExp(path, this.paramNames, this.opts)
}
Уровень отвечает за хранение информации о мониторинге маршрута, URL-адреса каждый раз, когда маршрут регистрируется, регулярного выражения, сгенерированного URL-адресом, параметров, существующих в URL-адресе, и промежуточного программного обеспечения, соответствующего маршруту.
все сданоLayer
Для сохранения основное внимание уделяется параметрам массива в процессе создания экземпляра:
- methods
- paramNames
- stack
methods
То, что хранится, является действительным, соответствующим мониторингу маршрутаMETHOD
, и будет нацелен во время создания экземпляраMETHOD
Выполните преобразование регистра.
paramNames
Из-за используемого плагина это выглядит не так однозначно, но на самом делеpathToRegExp
внутреннее собраниеparamNames
этот массивpush
операции, может быть удобнее смотреть на это такpathToRegExp(path, &this.paramNames, this.opts)
, в сплайсингеhash
Этот массив используется, когда параметр пути структуры
stack
Сохраняется функция промежуточного программного обеспечения, соответствующая прослушивателю маршрута,router.middleware
Часть логики будет зависеть от этого массива
path
Логика обработки в заголовке функции в основном предназначена для поддержки одновременной регистрации нескольких путей.Если найден первыйpath
После того, как параметр является массивом, он будет проходитьpath
параметры для вызова самого себя.
Итак, для несколькихURL
Та же самая маршрутизация может быть обработана следующим образом:
router.register(['/', ['/path1', ['/path2', 'path3']]], ['GET'], ctx => {
ctx.body = 'hi there.'
})
Это точно действительная установка:
> curl http://127.0.0.1:8888/
hi there.
> curl http://127.0.0.1:8888/path1
hi there.
> curl http://127.0.0.1:8888/path3
hi there.
methods
И оmethods
параметр, он считается массивом по умолчанию, даже если он слушает только одинMETHOD
Вам также необходимо передать массив в качестве параметра, если это пустой массив, даже еслиURL
совпадение, оно также будет пропущено напрямую, и будет выполнено следующее промежуточное ПО, которое будет выполнено в последующемrouter.routes
упоминается в
middleware
middleware
Это реальная реализация маршрутизации, и она по-прежнему соответствуетkoa
Может быть несколько стандартных промежуточных программ, которые выполняются в соответствии с луковой моделью.
Это тожеkoa-router
Самое важное место вURL
при исполнении.
Множественное промежуточное ПО, написанное здесь, предназначено для этого.URL
действительный.
P.S. вkoa-router
, также предоставляет метод, называемыйrouter.use
, это зарегистрируетrouter
ПО промежуточного слоя экземпляра
opts
opts
Он используется для установки некоторых правил конфигурации для генерации маршрута, включая следующие необязательные параметры:
Param | Type | Default | Description |
---|---|---|---|
name |
String |
- | Установите маршрут, соответствующийname ,имяrouter
|
prefix |
String |
- | Очень безвкусные параметры, совершенно бесполезные, вроде задает префикс маршрута, но на самом деле бесполезно |
sensitive |
Boolean |
false |
Следует ли строго соответствовать регистру, переопределить создание экземпляраRouter конфигурация в |
strict |
Boolean |
false |
Следует ли строго соблюдать регистр, если установлено значениеfalse затем соответствует следующему/ не является обязательным |
end |
Boolean |
true |
Соответствует ли путь концу полного URL-адреса |
ignoreCaptures |
Boolean |
- | Следует ли игнорировать группы захвата в маршруте, соответствующем обычным результатам. |
name
прежде всегоname
, в основном используется в этих местах:
- Более удобное позиционирование при возникновении исключения
- в состоянии пройти
router.url(<name>)
,router.route(<name>)
получить соответствующийrouter
Информация - Когда промежуточное ПО выполняется,
name
будет зажатctx.routerName
середина
router.register('/test1', ['GET'], _ => {}, {
name: 'module'
})
router.register('/test2', ['GET'], _ => {}, {
name: 'module'
})
console.log(router.url('module') === '/test1') // true
try {
router.register('/test2', ['GET'], null, {
name: 'error-module'
})
} catch (e) {
console.error(e) // Error: GET `error-module`: `middleware` must be a function, not `object`
}
если несколькоrouter
используйте то же имя, затем передайтеrouter.url
Вызов возвращает тот, который был зарегистрирован первым:
// route用来获取命名路由
Router.prototype.route = function (name) {
var routes = this.stack
for (var len = routes.length, i=0; i<len; i++) {
if (routes[i].name && routes[i].name === name) {
return routes[i] // 匹配到第一个就直接返回了
}
}
return false
}
// url获取该路由对应的URL,并使用传入的参数来生成真实的URL
Router.prototype.url = function (name, params) {
var route = this.route(name)
if (route) {
var args = Array.prototype.slice.call(arguments, 1)
return route.url.apply(route, args)
}
return new Error('No route found for name: ' + name)
}
Отвлекитесь, чтобы поговорить об этих вещах о router.url
Если в проекте вы хотите настроить таргетинг на некоторыеURL
прыгать, использоватьrouter.url
генерироватьpath
хороший выбор:
router.register(
'/list/:id', ['GET'], ctx => {
ctx.body = `Hi ${ctx.params.id}, query: ${ctx.querystring}`
}, {
name: 'list'
}
)
router.register('/', ['GET'], ctx => {
// /list/1?name=Niko
ctx.redirect(
router.url('list', { id: 1 }, { query: { name: 'Niko' } })
)
})
// curl -L http://127.0.0.1:8888 => Hi 1, query: name=Niko
можно увидеть,router.url
на самом деле звонитLayer
примерurl
метод, который в основном используется для обработки некоторых параметров, переданных при генерации.
Адрес источника:layer.js#L116
Функция принимает два параметра,params
а такжеoptions
, потому что самLayer
Экземпляр хранит соответствующийpath
информация, такая какparams
Это замена некоторых параметров, хранящихся в пути,options
В текущем коде есть только одинquery
поле, используемое для сращиванияsearch
Данные позади:
const Layer = require('koa-router/lib/layer')
const layer = new Layer('/list/:id/info/:name', [], [_ => {}])
console.log(layer.url({ id: 123, name: 'Niko' }))
console.log(layer.url([123, 'Niko']))
console.log(layer.url(123, 'Niko'))
console.log(
layer.url(123, 'Niko', {
query: {
arg1: 1,
arg2: 2
}
})
)
Все вышеперечисленные методы вызова допустимы, и в исходном коде есть соответствующая обработка. Первый — оценка нескольких параметров.params
Не одинobject
, будем считать, что черезlayer.url(参数, 参数, 参数, opts)
называется так.
преобразовать его вlayer.url([参数, 参数], opts)
Форма.
Логика в настоящее время должна иметь дело только с тремя случаями:
- Подстановка параметров в виде массива
-
hash
подстановка параметров формы - нет параметров
Эта замена параметра относится кURL
пройдетсторонние библиотекиИспользуется для обработки части параметра ссылки, т.е./:XXX
эту часть, затем перейдите вhash
Реализовать операции, аналогичные замене шаблона:
// 可以简单的认为是这样的操作:
let hash = { id: 123, name: 'Niko' }
'/list/:id/:name'.replace(/(?:\/:)(\w+)/g, (_, $1) => `/${hash[$1]}`)
потомlayer.url
Обработка заключается в генерации различных параметров, таких какhash
Такая структура со временем заменяетhash
получить полныйURL
.
prefix
экземпляр вышеLayer
в процессе появленияopts.prefix
Вес выше, но тогда есть логика суждения для вызова нижеsetPrefix
Переназначение, перерыв весь исходный код, я обнаружил, что разница только в том, что будетdebug
Заявка на регистрациюrouter
входящийprefix
, и везде будет создан экземплярRouter
времяprefix
Покрытый.
И если вы хотите направить правильное приложениеprefix
, вам нужно позвонитьsetPrefix
,Потому чтоLayer
Во время создания оpath
Хранилище от дальнего входящегоpath
параметр.
при подаче заявленияprefix
Префикс необходимо активировать вручнуюsetPrefix
:
// Layer实例化的操作
function Layer(path, methods, middleware, opts) {
// 省略不相干操作
this.path = path
this.regexp = pathToRegExp(path, this.paramNames, this.opts)
}
// 只有调用setPrefix才会应用前缀
Layer.prototype.setPrefix = function (prefix) {
if (this.path) {
this.path = prefix + this.path
this.paramNames = []
this.regexp = pathToRegExp(this.path, this.paramNames, this.opts)
}
return this
}
Это отражено в нескольких методах, предоставляемых пользователям, подобныхget
,set
так же какuse
.
Конечно, в документе также предусмотрено, что вы можете напрямую установить всеrouter
префиксный метод,router.prefix
:
В документации просто сказано, что вы можете установить префикс,prefix
Внутренне он вызовет всеlayer.setPrefix
:
router.prefix('/things/:thing_id')
Но глядя наlayer.setPrefix
После исходников я узнал, что тут на самом деле темная яма.
потому чтоsetPrefix
Реализация заключается в том, чтобы получитьprefix
параметры, сращенные с текущимиpath
голова.
Это создает проблему, если мы вызываем несколько разsetPrefix
вызовет несколькоprefix
Наложение, а не замена:
router.register('/index', ['GET'], ctx => {
ctx.body = 'hi there.'
})
router.prefix('/path1')
router.prefix('/path2')
// > curl http://127.0.0.1:8888/path2/path1/index
// hi there.
Префиксный метод накладывает префикс вместо переопределения префикса.
чувствительный и строгий
Об этих двух параметрах сказать нечего, т. е. они переопределяют конкретизацию.Router
Два параметра, передаваемые одновременно, имеют одинаковый эффект.
end
end
очень интересный параметр, это вkoa-router
Это отражено в других модулях, упомянутых в,path-to-regexp:
if (end) {
if (!strict) route += '(?:' + delimiter + ')?'
route += endsWith === '$' ? '$' : '(?=' + endsWith + ')'
} else {
if (!strict) route += '(?:' + delimiter + '(?=' + endsWith + '))?'
if (!isEndDelimited) route += '(?=' + delimiter + '|' + endsWith + ')'
}
return new RegExp('^' + route, flags(options))
endWith
Это может быть просто понято как регулярное$
, то есть конец матча.
Глядя на логику кода, грубо говоря, если установленоend: true
, то он в любом случае будет добавлен в конце$
Обозначает конец матча.
и еслиend: false
, то только если установить одновременноstrict: false
илиisEndDelimited: false
будет запущен.
Таким образом, мы можем добиться нечеткого сопоставления URL-адресов с помощью этих двух параметров:
router.register(
'/list', ['GET'], ctx => {
ctx.body = 'hi there.'
}, {
end: false,
strict: true
}
)
То есть окончательное регулярное выражение, сгенерированное вышеприведенным кодом для сопоставления маршрутов, вероятно, будет таким:
/^\/list(?=\/|$)/i
// 可以通过下述代码获取到正则
require('path-to-regexp').tokensToRegExp('/list/', {end: false, strict: true})
окончание$
является необязательным, что заставляет нас просто отправлять любые/list
Запрос будет получен этим промежуточным ПО.
ignoreCaptures
ignoreCaptures
Параметр используется, чтобы указать, следует ли возвращатьURL
в соответствующем параметре пути к промежуточному программному обеспечению.
и если установленоignoreCaptures
Эти два параметра станут пустыми объектами:
router.register('/list/:id', ['GET'], ctx => {
console.log(ctx.captures, ctx.params)
// ['1'], { id: '1' }
})
// > curl /list/1
router.register('/list/:id', ['GET'], ctx => {
console.log(ctx.captures, ctx.params)
// [ ], { }
}, {
ignoreCaptures: true
})
// > curl /list/1
Это вызывается во время выполнения промежуточного программного обеспечения изlayer
получен двумя способами.
первый звонокcaptures
Получить все параметры, если они установленыignoreCaptures
Это приведет к тому, что пустой массив будет возвращен напрямую.
тогда позвониparams
Передайте все параметры, сгенерированные при регистрации маршрута, и фактические значения параметров, а затем сгенерируйте полныйhash
вводить вctx
В объекте:
// 中间件的逻辑
ctx.captures = layer.captures(path, ctx.captures)
ctx.params = layer.params(path, ctx.captures, ctx.params)
ctx.routerName = layer.name
return next()
// 中间件的逻辑 end
// layer提供的方法
Layer.prototype.captures = function (path) {
if (this.opts.ignoreCaptures) return []
return path.match(this.regexp).slice(1)
}
Layer.prototype.params = function (path, captures, existingParams) {
var params = existingParams || {}
for (var len = captures.length, i=0; i<len; i++) {
if (this.paramNames[i]) {
var c = captures[i]
params[this.paramNames[i].name] = c ? safeDecodeURIComponent(c) : c
}
}
return params
}
// 所做的事情大致如下:
// [18, 'Niko'] + ['age', 'name']
// =>
// { age: 18, name: 'Niko' }
Роль router.param
Выше было описание некоторых параметров при регистрации маршрутов, вы можете увидеть вregister
экземпляр вLayer
Ведь объект не ставится прямоstack
В середине он будет только выталкиваться в этой операции.stack
:
Object.keys(this.params).forEach(function (param) {
route.param(param, this.params[param])
}, this)
stack.push(route) // 装载
Здесь используется для добавленияURL
Аргументы, обрабатываемые промежуточным программным обеспечением, сrouter.param
Эти два тесно связаны:
Router.prototype.param = function (param, middleware) {
this.params[param] = middleware
this.stack.forEach(function (route) {
route.param(param, middleware)
})
return this
}
Эти две операции похожи, первая используется для добавления всех новых прослушивателей маршрута.param
Промежуточное ПО, а последнее добавляется ко всем существующим маршрутамparam
промежуточное ПО.
Потому чтоrouter.param
имеет вthis.params[param] = XXX
операция присваивания.
Таким образом, при последующем мониторинге нового маршрута зацикливайте напрямуюthis.params
Вы можете получить все промежуточное ПО.
router.param
Операции также описаны в документе,адрес документа
Грубо говоря, его можно использовать для выполнения некоторых операций, таких как проверка параметров, но поскольку вlayer.param
есть немногоспециальная обработка, так что нам не о чем беспокоитьсяparam
порядок исполнения,layer
будет гарантироватьparam
Должен выполняться перед промежуточным программным обеспечением, которое зависит от этого параметра:
router.register('/list/:id', ['GET'], (ctx, next) => {
ctx.body = `hello: ${ctx.name}`
})
router.param('id', (param, ctx, next) => {
console.log(`got id: ${param}`)
ctx.name = 'Niko'
next()
})
router.param('id', (param, ctx, next) => {
console.log('param2')
next()
})
// > curl /list/1
// got id: 1
// param2
// hello: Niko
Наиболее часто используемые ярлыки, такие как get/post
И закончил основной метод вышеregister
, мы можем взглянуть на несколько представленных разработчикамrouter.verb
метод:
// get|put|post|patch|delete|del
// 循环注册多个METHOD的快捷方式
methods.forEach(function (method) {
Router.prototype[method] = function (name, path, middleware) {
let middleware
if (typeof path === 'string' || path instanceof RegExp) {
middleware = Array.prototype.slice.call(arguments, 2)
} else {
middleware = Array.prototype.slice.call(arguments, 1)
path = name
name = null
}
this.register(path, [method], middleware, {
name: name
})
return this
}
})
Router.prototype.del = Router.prototype['delete'] // 以及最后的一个别名处理,因为del并不是有效的METHOD
Это разочаровывает,verb
метод будет многоopts
Параметры обрезаны, оставлены только значения по умолчаниюname
поле.
Это просто очень простой процесс именованияname
Логика, связанная с маршрутизацией, а затем вызовregister
Завершите операцию.
router.use — промежуточное ПО внутри маршрутизатора
И упоминалось вышеrouter.use
Его можно использовать для регистрации использования промежуточного программного обеспечения.use
Промежуточное ПО для регистрации делится на два случая:
- Обычная промежуточная функция
- поставить существующий
router
Экземпляры передаются как промежуточное ПО
обычное использование
вотuse
Ключевой код метода:
Router.prototype.use = function () {
var router = this
middleware.forEach(function (m) {
if (m.router) { // 这里是通过`router.routes()`传递进来的
m.router.stack.forEach(function (nestedLayer) {
if (path) nestedLayer.setPrefix(path)
if (router.opts.prefix) nestedLayer.setPrefix(router.opts.prefix) // 调用`use`的Router实例的`prefix`
router.stack.push(nestedLayer)
})
if (router.params) {
Object.keys(router.params).forEach(function (key) {
m.router.param(key, router.params[key])
})
}
} else { // 普通的中间件注册
router.register(path || '(.*)', [], m, { end: false, ignoreCaptures: !hasPath })
}
})
}
// 在routes方法有这样的一步操作
Router.prototype.routes = Router.prototype.middleware = function () {
function dispatch() {
// ...
}
dispatch.router = this // 将router实例赋值给了返回的函数
return dispatch
}
Первый - более традиционный способ, передача функции, необязательнаяpath
, чтобы зарегистрировать промежуточное ПО.
Однако следует отметить, что.use('path')
Для такого использования промежуточное ПО не может существовать независимо, и должен быть прослушиватель маршрута, который может соответствовать его пути:
router.use('/list', ctx => {
// 如果只有这么一个中间件,无论如何也不会执行的
})
// 必须要存在相同路径的`register`回调
router.get('/list', ctx => { })
app.use(router.routes())
Причина в следующем:
-
.use
а также.get
основаны на.register
добиться, но.use
существуетmethods
Переданный параметр является пустым массивом - Когда путь совпадает, все подходящее промежуточное ПО будет удалено, а затем соответствующий
methods
,еслиlength !== 0
Это пометит группу для текущего матчаflag
- Прежде чем выполнять промежуточное ПО, оно сначала определит, есть ли это
flag
, если нет, значит не все мидлвары в этом пути установленыMETHOD
, он сразу перейдет к другим процессам (например, разрешенный метод)
Router.prototype.match = function (path, method) {
var layers = this.stack
var layer
var matched = {
path: [],
pathAndMethod: [],
route: false
}
for (var len = layers.length, i = 0; i < len; i++) {
layer = layers[i]
if (layer.match(path)) {
matched.path.push(layer)
if (layer.methods.length === 0 || ~layer.methods.indexOf(method)) {
matched.pathAndMethod.push(layer)
// 只有在发现不为空的`methods`以后才会设置`flag`
if (layer.methods.length) matched.route = true
}
}
}
return matched
}
// 以及在`routes`中有这样的操作
Router.prototype.routes = Router.prototype.middleware = function () {
function dispatch(ctx, next) {
// 如果没有`flag`,直接跳过
if (!matched.route) return next()
}
return dispatch
}
Передать в другие экземпляры маршрутизатора
Вы можете увидеть это, если вы выберетеrouter.routes()
Чтобы мультиплексировать промежуточное ПО, вы пройдете все маршруты этого экземпляра, а затем установитеprefix
.
и будет измененоlayer
выкатиться на текущийrouter
середина.
Итак, теперь мы должны обратить внимание, это уже было упомянуто выше,Layer
изsetPrefix
Прошита, не обтянута.
а такжеuse
будет работатьlayer
объект, поэтому такое использование также приведет к изменению предыдущего пути к промежуточному ПО.
И если вы пройдете вuse
Промежуточное ПО уже зарегистрировано вkoa
приведет к тому, что одно и то же промежуточное ПО будет выполняться дважды (если есть звонокnext
если):
const middlewareRouter = new Router()
const routerPage1 = new Router({
prefix: '/page1'
})
const routerPage2 = new Router({
prefix: '/page2'
})
middlewareRouter.get('/list/:id', async (ctx, next) => {
console.log('trigger middleware')
ctx.body = `hi there.`
await next()
})
routerPage1.use(middlewareRouter.routes())
routerPage2.use(middlewareRouter.routes())
app.use(middlewareRouter.routes())
app.use(routerPage1.routes())
app.use(routerPage2.routes())
Как и в приведенном выше коде, на самом деле есть две проблемы:
- Окончательный допустимый путь доступа:
/page2/page1/list/1
,потому чтоprefix
Будет прошит вместо чехла - Когда мы вызываем промежуточное ПО
next
после,console.log
будет выведено три раза подряд, потому что всеroutes
на самом деле все динамическиеprefix
были изменены на/page2/page1
Обязательно используйте его осторожно, не думайте, что этот метод можно использовать для повторного использования маршрутизации.
обработка запросов
И, наконец, дошел до последнего шага, когда пришел запрос,Router
как это обрабатывается.
ОдинRouter
Экземпляры могут создавать два промежуточных ПО, зарегистрированных наkoa
начальство:
app.use(router.routes())
app.use(router.allowedMethods())
routes
Отвечает за основную логику.
allowedMethods
отвечает за предоставление постаMETHOD
Проверьте промежуточное ПО.
allowedMethods
Нечего сказать, основано на текущем запросеmethod
Выполняются некоторые проверки и возвращаются некоторые сообщения об ошибках.
И многие из представленных выше методов на самом деле предназначены для окончательногоroutes
Служить:
Router.prototype.routes = Router.prototype.middleware = function () {
var router = this
var dispatch = function dispatch(ctx, next) {
var path = router.opts.routerPath || ctx.routerPath || ctx.path
var matched = router.match(path, ctx.method)
var layerChain, layer, i
if (ctx.matched) {
ctx.matched.push.apply(ctx.matched, matched.path)
} else {
ctx.matched = matched.path
}
ctx.router = router
if (!matched.route) return next()
var matchedLayers = matched.pathAndMethod
var mostSpecificLayer = matchedLayers[matchedLayers.length - 1]
ctx._matchedRoute = mostSpecificLayer.path
if (mostSpecificLayer.name) {
ctx._matchedRouteName = mostSpecificLayer.name
}
layerChain = matchedLayers.reduce(function(memo, layer) {
memo.push(function(ctx, next) {
ctx.captures = layer.captures(path, ctx.captures)
ctx.params = layer.params(path, ctx.captures, ctx.params)
ctx.routerName = layer.name
return next()
})
return memo.concat(layer.stack)
}, [])
return compose(layerChain)(ctx, next)
};
dispatch.router = this
return dispatch
}
Сначала вы можете увидеть, чтоkoa-router
Также предоставляет псевдонимmiddleware
для достижения той же функции.
И вызов функции в конечном итоге вернет функцию промежуточного программного обеспечения, эта функция действительно привязана кkoa
Вверх.
koa
Промежуточное ПО является чистым промежуточным ПО, а включенное промежуточное ПО выполняется независимо от запроса.
Поэтому не рекомендуется использоватьprefix
при создании несколькихRouter
экземпляр, что приводит кkoa
Смонтировать несколько наdispatch
Используется для проверки того, соответствует ли URL-адрес правилам
После входа в промежуточное программное обеспечение будет оцениваться URL-адрес, для чего мы упоминали выше.foraward
где это реализовано.
Соответствующий вызовrouter.match
метод, хотя кажется, что заданиеmatched.path
, а на самом делеmatch
В реализации метода все они сопоставлены.Layer
Пример:
Router.prototype.match = function (path, method) {
var layers = this.stack // 这个就是获取的Router实例中所有的中间件对应的layer对象
var layer
var matched = {
path: [],
pathAndMethod: [],
route: false
}
for (var len = layers.length, i = 0; i < len; i++) {
layer = layers[i]
if (layer.match(path)) { // 这里就是一个简单的正则匹配
matched.path.push(layer)
if (layer.methods.length === 0 || ~layer.methods.indexOf(method)) {
// 将有效的中间件推入
matched.pathAndMethod.push(layer)
// 判断是否存在METHOD
if (layer.methods.length) matched.route = true
}
}
}
return matched
}
// 一个简单的正则匹配
Layer.prototype.match = function (path) {
return this.regexp.test(path)
}
И причина, по которой он существует, состоит в том, чтобы судить о том, существует лиctx.matched
вместо того, чтобы присваивать значение непосредственно этому свойству.
Это связано с тем, что, как было сказано выше,koa
Экземпляры могут регистрировать несколькоkoa-router
пример.
Это приводит кrouter
После выполнения промежуточного программного обеспечения экземпляра могут быть другие последующие действия.router
экземпляр также попал вURL
, но это гарантируетmatched
Он всегда накапливается, а не перезаписывается каждый раз.
path
а такжеpathAndMethod
обеmatch
Два возвращенных массива, разница между ними в том, чтоpath
Возвращаются данные, которые успешно соответствуют URL-адресу, иpathAndMethod
Это данные, которые соответствуют URL-адресу и соответствуют МЕТОДУ.
const router1 = new Router()
const router2 = new Router()
router1.post('/', _ => {})
router1.get('/', async (ctx, next) => {
ctx.redirectBody = 'hi'
console.log(`trigger router1, matched length: ${ctx.matched.length}`)
await next()
})
router2.get('/', async (ctx, next) => {
ctx.redirectBody = 'hi'
console.log(`trigger router2, matched length: ${ctx.matched.length}`)
await next()
})
app.use(router1.routes())
app.use(router2.routes())
// > curl http://127.0.0.1:8888/
// => trigger router1, matched length: 2
// => trigger router2, matched length: 3
Что касается выполнения промежуточного программного обеспечения, вkoa-router
также используется вkoa-compose
Чтобы объединить лук:
var matchedLayers = matched.pathAndMethod
layerChain = matchedLayers.reduce(function(memo, layer) {
memo.push(function(ctx, next) {
ctx.captures = layer.captures(path, ctx.captures)
ctx.params = layer.params(path, ctx.captures, ctx.params)
ctx.routerName = layer.name
return next()
})
return memo.concat(layer.stack)
}, [])
return compose(layerChain)(ctx, next)
Этот фрагмент кода будет предварять все подходящие промежуточные программы символомctx
Промежуточная операция назначения атрибута, т.е.reduce
Выполнение луковой модели сделает количество функций промежуточного программного обеспечения, соответствующих луковой модели, не менееX2
.
Слой может содержать несколько промежуточных программ, не забывайтеmiddleware
, именно поэтомуreduce
используется вconcat
вместоpush
Поскольку перед выполнением каждого промежуточного программного обеспечения изменитеctx
Это некоторая информация, когда промежуточное программное обеспечение срабатывает.
Включая совпадающие параметры URL, а также текущее промежуточное ПО.name
информация, такая как.
[
layer1[0], // 第一个register中对应的中间件1
layer1[1], // 第一个register中对应的中间件2
layer2[0] // 第二个register中对应的中间件1
]
// =>
[
(ctx, next) => {
ctx.params = layer1.params // 第一个register对应信息的赋值
return next()
},
layer1[0], // 第一个register中对应的中间件1
layer1[1], // 第一个register中对应的中间件2
(ctx, next) => {
ctx.params = layer2.params // 第二个register对应信息的赋值
return next()
},
layer2[0] // 第二个register中对应的中间件1
]
существуетroutes
Наконец, он вызоветkoa-compose
объединитьreduce
Сгенерированный массив промежуточного программного обеспечения, а такжеkoa-compose
Вторые необязательные параметры, упомянутые в середине, используются для выполнения конечного процесса обратного вызова после выполнения лука.
Примечания
Слишком далеко,koa-router
Миссия выполнена, реализована регистрация маршрута, мониторинг и обработка маршрута.
чтениеkoa-router
Процесс исходного кода очень запутан:
- Почему реализованная в коде функция не отражена в документе?
- Если в документации не сказано, что это можно использовать таким образом, то зачем в коде должна быть соответствующая реализация?
Два простейших доказательства:
- может быть изменен с помощью
ctx.routerPath
реализоватьforward
функция, но она не скажет вам в документации - в состоянии пройти
router.register(path, ['GET', 'POST'])
быстро контролировать несколькоMETHOD
,ноregister
отмечен как@private
Использованная литература:
Расположение примера кода в репозитории:learning-koa-router