03 路由匹配

news2024/11/18 23:29:05

封装了框架的 Context, 将请求结构 request 和返回结构 responseWriter 都封装在 Context 中。利用这个 Context, 我们将控制器简化为带有一个参数的函数 FooControllerHandler,这个控制器函数的输入和输出都是固定的。在框架层面,我们也定义了对应关于控制器的方法结构 ControllerHandler 来代表这类控制器的函数。

每一个请求逻辑,都有一个控制器 ControllerHandler 与之对应。那么一个请求,如何查找到指定的控制器呢?

路由

路由的功能,具体来说就是让 Web 服务器根据规则,理解 HTTP 请求中的信息,匹配查找出对应的控制器,再将请求传递给控制器执行业务逻辑,简单来说就是制定匹配规则。

一个 HTTP 请求包含请求头和请求体。请求体内一般存放的是请求的业务数据,是基于具体控制业务需要的,所以,我们不会用来做路由。

而请求头中存放的是和请求状态有关的信息,比如 User-Agent 代表的是请求的浏览器信息,Accept 代表的是支持返回的文本类型。以下是一个标准请求头的示例:

GET /home.html HTTP/1.1
Host: developer.mozilla.org
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:50.0) Gecko/20100101 Firefox/50.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: https://developer.mozilla.org/testpage.html

每一行的信息和含义都是非常大的课题, 这里重点讲第一行,叫做Request Line,由Method、Request-URI 和 HTTP-Version三部分组成:
在这里插入图片描述
Method 是 HTTP 的方法,标识对服务端资源的操作属性。

 Method         = "OPTIONS"                ; Section 9.2
                | "GET"                    ; Section 9.3
                | "HEAD"                   ; Section 9.4
                | "POST"                   ; Section 9.5
                | "PUT"                    ; Section 9.6
                | "DELETE"                 ; Section 9.7
                | "TRACE"                  ; Section 9.8
                | "CONNECT"                ; Section 9.9
                | extension-method
 extension-method = token

Request-URI 是请求路径,也就是浏览器请求地址中域名外的剩余部分。

在这里插入图片描述
HTTP-Version 是 HTTP 的协议版本,目前常见的有 1.0、1.1、2.0。

Web Service 在路由中使用的就是 Method 和 Request-URI 这两个部分。如果框架支持 REST 风格的路由设计,那么使用者在写业务代码的时候,就倾向于设计 REST 风格的接口;如果框架支持前缀匹配,那么使用者在定制 URI 的时候,也会倾向于把同类型的 URI 归为一类。

这些设计想法通通会体现在框架的路由规则上,最终影响框架使用者的研发习惯,这个就是设计感。

路由规则的需求

我们希望使用者高效、易用地使用路由模块,那出于这一点考虑,基本需求可以有哪些呢?

按照从简单到复杂排序,路由需求有下面四点:

  • HTTP 方法匹配
    早期简单的WebService只用到Request-URI部分,随着REST风格的流行,也需要支持多种HTTP Method

  • 静态路由匹配
    静态路由匹配是一个路由的基本功能,指的是路由规则中没有可变参数,即路由规则地址是固定的,与 Request-URI 完全匹配。
    net/http包中的DefaultServerMux 这个路由器,从内部的 map 中直接根据 key 寻找 value ,这种查找路由的方式就是静态路由匹配。

  • 批量通用前缀
    为某个业务模块注册一批路由,比如/user/info、 /user/login都是以/user开头。
    所以如果路由有能力统一定义批量的通用前缀,那么在注册路由的过程中,会带来很大的便利。

  • 动态路由匹配
    针对静态路由改进,因为URL中某些字段并不是固定的,而是按照一定规则(如数字)变化。

功能Request-URIHTTP Method
用户登录/user/loginPOST
增加专题/subject/addPOST
删除专题/subject/1DELETE
修改专题/subject/1PUT
查找专题/subject/1GET
获取专题列表/subject/listGET

那么该如何实现这几个需求呢?

需求实现

实现 HTTP 方法和静态路由匹配

对于第一、二个需求,很容易想到使用两层哈希表实现:

func NewCore() *Core {
	getRouter := map[string]ControllerHandler{}
	postRouter := map[string]ControllerHandler{}
	putRouter := map[string]ControllerHandler{}
	deleteRouter := map[string]ControllerHandler{}

	router := map[string]map[string]ControllerHandler{}
	router["GET"] = getRouter
	router["POST"] = postRouter
	router["PUT"] = putRouter
	router["DELETE"] = deleteRouter
	return &Core{router: router}
}


// 对应 Method = Get
func (c *Core) Get(url string, handler ControllerHandler) {
	upperUrl := strings.ToUpper(url)
	c.router["GET"][upperUrl] = handler
}

// 匹配路由,如果没有匹配到,返回nil
func (c *Core) FindRouteByRequest(request *http.Request) ControllerHandler {
	// uri 和 method 全部转换为大写,保证大小写不敏感
	uri := request.URL.Path
	method := request.Method
	upperMethod := strings.ToUpper(method)
	upperUri := strings.ToUpper(uri)

	// 查找第一层map
	if methodHandlers, ok := c.router[upperMethod]; ok {
		// 查找第二层map
		if handler, ok := methodHandlers[upperUri]; ok {
			return handler
		}
	}
	return nil
}

func (c *Core) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	ctx := NewContext(r, w)
	router := c.FindRouteByRequest(r)
	if router == nil {
		ctx.Json(404, "not found")
		return
	}

	if err := router(ctx); err != nil {
		ctx.Json(500, "inner error")
		return
	}
}

批量实现通用前缀

批量通用前缀,类似下面这样使用:


// 注册路由规则
func registerRouter(core *framework.Core) {
  // 需求3:批量通用前缀
  subjectApi := core.Group("/subject")
  {
    subjectApi.Get("/list", SubjectListController)
  }
}

这里core.Group方法接收一个字符串前缀,返回的是一个包含Get、Post、Put、Delete方法的结构,我们将其命名为Group,因为是一个实现了各种方法的结构,因此我们这里采用接口定义IGroup:

// IGroup 代表前缀分组
type IGroup interface {
  Get(string, ControllerHandler)
  Post(string, ControllerHandler)
  Put(string, ControllerHandler)
  Delete(string, ControllerHandler)
}


// Group struct 实现了IGroup
type Group struct {
  core   *Core
  prefix string
}

// 初始化Group
func NewGroup(core *Core, prefix string) *Group {
  return &Group{
    core:   core,
    prefix: prefix,
  }
}

// 实现Get方法
func (g *Group) Get(uri string, handler ControllerHandler) {
  uri = g.prefix + uri
  g.core.Get(uri, handler)
}

....

// 从core中初始化这个Group
func (c *Core) Group(prefix string) IGroup {
  return NewGroup(c, prefix)
}

实现动态路由

希望实现如下所示的动态路由:

func registerRouter(core *framework.Core) {
  // 需求1+2:HTTP方法+静态路由匹配
  core.Get("/user/login", UserLoginController)

  // 需求3:批量通用前缀
  subjectApi := core.Group("/subject")
  {
    // 需求4:动态路由
    subjectApi.Delete("/:id", SubjectDelController)
    subjectApi.Put("/:id", SubjectUpdateController)
    subjectApi.Get("/:id", SubjectGetController)
    subjectApi.Get("/list/all", SubjectListController)
  }
}

引入动态路由,则之前的哈希规则无法使用了,因为字符是动态变化的,无法使用URI作为key来匹配。

这个问题本质是一个字符串匹配

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/352976.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL(二)表的操作

一、创建表 CREATE TABLE table_name ( field1 datatype, field2 datatype, field3 datatype ) character set 字符集 collate 校验规则 engine 存储引擎; 说明: field 表示列名 datatype 表示列的类型 character set 字符集,如…

如何创建你的公司的FAQ页面?

很多企业考虑为公司搭建一个“常见问题”页面,作为帮助客户回答关于产品和服务的常见问题的一种方式。 FAQ页面和登录/销售页面不同,没有展现出直接的投资回报,但是为团队节省了其他成本,据了解,高达67%的客户相比于跟…

STM32 - 看门狗

独立看门狗 IWDG专业时钟LSI 低功耗仍可以运行对定时的控制比较松喂狗这些时间是按照40kHz时钟给出。实际上,MCU内部的RC频率会在30kHz到60kHz之间变化。此外,即使RC振荡器的频率是精确的,确切的时序仍然依赖于APB接口时钟与RC振荡器时钟之间…

浅谈IDE 和代码编辑器之间有什么区别?

您希望如何完成日常编码?快速地?明显地。以既不重复也不单调的方式?自然。拥有您可能随时需要的所有工具。 是否会得到这一切取决于选择在哪里编写代码。您在这里的两个主要选择是代码编辑器或 IDE。两者都旨在使您的编码更容易 - 但是&…

使用 PyNeuraLogic 超越 Transformers

展示神经符号编程的力量neuro-symbolic1. 简介 在过去的几年里,我们看到了基于 Transformer 的模型的兴起,并在自然语言处理或计算机视觉等许多领域取得了成功的应用。在本文[1]中,我们将探索一种简洁、可解释和可扩展的方式来表达深度学习模…

捕获最小化窗口的缩略图画面

关键字: capture minimized window window thumbnail IsIconic 问题背景 最小化的窗口,API GetClientRect 返回的窗口尺寸是0x0,故无法通过GetDCBitBlt捕获到窗口画面。 但是 Agora/zoom/tencentMeeting 都可以拿到最小化窗口的缩略图…

Python自动化必不可少的测试框架 — pytest

Python在测试圈的应用非常广泛,特别是在自动化测试以及测试开发的领域,其中在自动化测试中我们常用的测试框架是uniitest和pytest,本文将带领大家搭建以及熟悉pytest的使用。 既然有unittest那么为什么还要用pytest呢? 这是因为…

会议论文分享-Security22-状态感知符号执行

Ferry: State-Aware Symbolic Execution for Exploring State-Dependent Program Paths1.引言2.问题陈述与分析2.1.实现状态感知符号执行的挑战2.2.真实程序的特征2.3.Ferry的模型2.3.1.程序状态的定义2.3.2.状态描述变量的特征3.Design3.1.Overview of Ferry3.2.状态描述变量识…

CISCN(Web Ezpentest)GC、序列化、case when

目录 REGEXP的一个点(正则) like(默认不区分大小写) 当禁用了空格 regexp,like的区分大小写的使用方法 [CISCN 2022 初赛]ezpentest 卡点 2022 HFCTF babysql 最近又学到了一道新知识,case when的错…

Python|每日一练|排序|递归|链表|字符串|数组|动态规划|哈希表|单选记录:K 个一组翻转链表|括号生成|无重复字符的最长子串

1、K 个一组翻转链表(递归,链表) 给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。 k 是一个正整数,它的值小于或等于链表的长度。 如果节点总数不是 k 的整数倍,那么请将最后剩…

kafka安装及使用

目录 1.单机部署 1.下载安装包 2.启动zookeeper服务 3.启动kafka broker服务 2.集群部署 1.下载安装包 2.修改zookeeper配置 3.修改kafka配置 4.在3台机器上启动zookepper 5.在3台机器上启动kafka 6.检查是否启动OK 3.使用 1.创建topic 2.查看kafka topic分区数 …

OpenFaaS介绍

FaaS 云计算时代出现了大量XaaS形式的概念,从IaaS(Infrastructure as a Service)、PaaS(Platform as a Service)、SaaS(Software as a Service)到容器云引领的CaaS(Containers as a Service),再到火热的微服务架构,它们都在试着将各种软、硬…

通过Prowork每日自动提醒待处理工作任务

对于中小团队来说,由于不需要繁琐的流程和高频的异地沟通,需要一款更适合中小团队的日程和项目管理工具。而Prowork就是这样一款敏捷高效的协同平台。Prowork与以往各种项目管理⼯具最⼤的不同在于,其弱化流程和弱化权限的特性,不…

ES6基础规范

1.变量声明const和let 我们都是知道在ES6以前,var关键字声明变量。无论声明在何处,都会被视为声明在函数的最顶部(不在函数内即在全局作用域的最顶部)。这就是函数变量提升例如: 下图左侧其实相当于右侧: 所以不用关心bool是否为true or false。实际上…

小知识点:Mac M1/M2 VMware Fusion 安装 Centos 7.9(ARM 64 版本)

最近换了 Mac M2 芯片的笔记本,用原来的 Centos 镜像安装虚拟机直接报错 “无法打开此虚拟机的电源,因为它需要使用 X86 计算机架构,而该架构与此 Arm 计算机架构主机不兼容。” 安装流程前置一、下载镜像二、安装虚拟机三、配置静态 IP四、安…

UVa 225 Golygons 黄金图形 暴力搜索 剪枝 状态判断

题目链接:Golygons 题目描述: 给定nnn和kkk个障碍物的坐标,你需要走nnn次,第一次走一个单位距离,第二次走二个单位距离,…,第nnn次走nnn个单位距离。走得过程中不能穿过或者到达障碍物所在的点&…

数据分析与SAS学习笔记6

数据集整理: 目的:对数据集中的数据进行预处理,使数据更适合统计分析过程对数据格式的要求; 常见整理要求: 1)建立新的变量,衍生变量,删除某些原变量; 2)…

Jira Server一年后“停服” 中国用户如何减损失降影响

近日Jira官方再次表示:在“2024年2月15日之后,用户所使用的 Server 版本的安全漏洞将不再有官方的修复方案,产品的 Bug 也不再被修复。在支持日期结束后,Atlassian 和Marketplace 合作伙伴将不再为任何问题、安全更新或关键漏洞的…

spring-web DispatcherServlet 源码分析

说明 本文基于 jdk 8, spring-framework 5.2.x 编写。author JellyfishMIX - github / blog.jellyfishmix.comLICENSE GPL-2.0 DispatcherServlet 的继承实现层次 关注点应放在 Servlet, GenericServlet, HttpServlet, HttpServletBean, FrameworkServlet, DispatcherServle…

关于Ubuntu20.04文件系统思考

文章目录问题产生Ubuntu文件系统中普通用户可读写地址Ubuntu文件系统Ubuntu文件系统详解一级目录二级目录查找Ubuntu中软件安装位置Ubuntu修改文件权限问题产生 使用electron框架开发桌面端跨平台软件时,当开发完成的程序部署到Ubuntu上,系统无法产生日…