UNPKG

@halsp/http

Version:

支持 Halsp HTTP 请求

193 lines (170 loc) 5.45 kB
import { Context, Dict, normalizePath, Register } from "@halsp/core"; import { HttpMethods } from "./methods"; import { HttpOptions } from "./options"; export interface ParsedRegister { methods: string[]; url: string; handler?: Register["handler"]; } export class MapMatcher { constructor( private readonly ctx: Context, private readonly options: HttpOptions, ) { this.#parsedRegisters = ctx.startup.parsedRegisters; } #parsedRegisters!: ParsedRegister[]; public async match() { const register = this.getRegister(); if (!register) return; parseParams(this.ctx, register); if (register.handler) { await register.handler(this.ctx); } } private getRegister(): ParsedRegister | undefined { const matchedPaths = this.#parsedRegisters .filter((r) => !!r.methods.length) .filter((r) => this.isPathMatched(r, true)); this.#parsedRegisters .filter((r) => !r.methods.length || r.methods.includes(HttpMethods.any)) .filter((m) => this.isPathMatched(m, false)) .forEach((m) => matchedPaths.push(m)); const mapItem = this.getMostLikeRegister(matchedPaths); if (mapItem) return mapItem; const otherMethodPathCount = this.#parsedRegisters.filter((r) => this.isPathMatched(r, false), ).length; if (otherMethodPathCount) { const method = this.ctx.req.method; this.ctx.res.methodNotAllowedMsg({ message: `method not allowed:${method}`, method: method, path: this.ctx.req.path, }); } else { this.ctx.res.notFoundMsg({ message: `Can't find the path:${this.ctx.req.path}`, path: this.ctx.req.path, }); } } private isPathMatched( register: ParsedRegister, methodIncluded: boolean, ): boolean { const reqUrl = this.ctx.req.path; const reqUrlStrs = reqUrl .toLowerCase() .split("/") .filter((item) => !!item); const mapUrlStrs = register.url .toLowerCase() .split("/") .filter((item) => !!item); if (reqUrlStrs.length != mapUrlStrs.length) return false; if (methodIncluded && !register.methods.includes(HttpMethods.any)) { const matchedMethod = HttpMethods.matched( this.ctx.req.method, this.options.customMethods, ); if (!matchedMethod || !register.methods.includes(matchedMethod)) { return false; } } for (let i = 0; i < mapUrlStrs.length; i++) { if (mapUrlStrs[i] != reqUrlStrs[i] && !mapUrlStrs[i].startsWith("^")) { return false; } } return true; } private getMostLikeRegister( registers: ParsedRegister[], ): ParsedRegister | undefined { if (!registers || !registers.length) return; if (registers.length == 1) return registers[0]; const pathsParts = <{ register: ParsedRegister; parts: string[] }[]>[]; registers.forEach((register) => { pathsParts.push({ register: register, parts: register.url .toLowerCase() .split("/") .filter((item) => !!item), }); }); const minPartsCount = Math.min(...pathsParts.map((pp) => pp.parts.length)); for (let i = 0; i < minPartsCount; i++) { if ( pathsParts.some((p) => p.parts[i].includes("^")) && pathsParts.some((p) => !p.parts[i].includes("^")) ) { pathsParts .filter((p) => p.parts[i].includes("^")) .forEach((p) => pathsParts.splice(pathsParts.indexOf(p), 1)); } if (pathsParts.length == 1) return pathsParts[0].register; } if ( pathsParts.some((pp) => pp.register.methods.includes(HttpMethods.any)) && pathsParts.some((pp) => !pp.register.methods.includes(HttpMethods.any)) ) { pathsParts .filter((pp) => pp.register.methods.includes(HttpMethods.any)) .forEach((pp) => { pathsParts.splice(pathsParts.indexOf(pp), 1); }); } return pathsParts.sort((a, b) => a.parts.length - b.parts.length)[0] .register; } } export function parsePattern(register: Register): ParsedRegister { const strs = register.pattern.trim().split("//"); if (strs.length <= 1) { return { methods: [], url: normalizePath(strs[0]), handler: register.handler, }; } else { return { methods: strs[0] .split(",") .filter((m) => !!m) .map((m) => m.toUpperCase()), url: normalizePath(strs[1]), handler: register.handler, }; } } function parseParams(ctx: Context, register: ParsedRegister) { const params: Dict<string> = {}; if (register.url.includes("^")) { const mapPathStrs = register.url.split("/").filter((item) => !!item); const reqPathStrs = ctx.req.path.split("/").filter((item) => !!item); for (let i = 0; i < Math.min(mapPathStrs.length, reqPathStrs.length); i++) { const mapPathStr = mapPathStrs[i]; if (!mapPathStr.startsWith("^")) continue; const reqPathStr = reqPathStrs[i]; const key = mapPathStr.substring(1, mapPathStr.length); const value = decodeURIComponent(reqPathStr); params[key] = value; } } Object.defineProperty(ctx.req, "params", { configurable: true, enumerable: true, get: () => { return params; }, }); Object.defineProperty(ctx.req, "param", { configurable: true, enumerable: true, get: () => { return params; }, }); }