Golang中HTTP路由设计的使用与实现

目录
  • Golang之HTTP路由设计
  • 动手编写自己的路由
    • framework/core.go
    • framework/group.go
    • 如何实现动态路由
    • 改造一下core.go
  • 验证

Golang之HTTP路由设计

为什么要设计路由规则,路由规则是HTTP的请求按照一定的规则 ,匹配查找到对应的控制器并传递执行的逻辑!

自己编写路由的话需要注意一下一共有几种路由!

  • 一种是支持原生的restful四种类型的访问方法!Get,Post,Delete,Put
  • 需要支持自定义的路径,也就是静态路由
  • 批量通用前缀,也就是下面我们将讲到的group
  • 动态路由匹配!

也就是像这样我们在route.go去注册

func registerRouter(core *framework.Core) {
	print(111)
	// 设置控制器
	core.Get("/foo", FooController)
	core.Get("/user/login", UserLoginController)
	subjectApi := core.Group("/subject")
	{
    // restful路由,根据请求类型区分了开,:id为动态路由
		subjectApi.Get("/list/all", SubjectListController)
		subjectApi.Post("/add", SubjectListController)
		subjectApi.Delete("/:id", SubjectListController)
		subjectApi.Put("/:id", SubjectListController)
		subjectApi.Get("/:id", SubjectListController)
	}
}

动手编写自己的路由

在上一节中我们编写了自己的请求处理器,对应在里面加入我们的路由规则就好了!

framework/core.go

package framework
import (
	"net/http"
	"strings"
)
const (
	GET    = "GET"
	PUT    = "PUT"
	DELETE = "DELETE"
	POST   = "POST"
)
//map[string]map[string]ControllerHandler 前面存请求类型后面是路径对应执行方法
type Core struct {
	router map[string]map[string]ControllerHandler
}
func (c Core) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
	ctx:= NewContext(request, writer)
	router:=c.FindRouteByRequest(request)
	if router==nil{
		ctx.Json(404,"router not found ")
	    return
	}
	if err:=router(ctx);err!=nil{
		ctx.Json(500,"server Interval")
		return
	}
	//http.DefaultServeMux.ServeHTTP(writer, request)
}
func NewCore() *Core {
	getRouter := map[string]ControllerHandler{}
	postRouter := map[string]ControllerHandler{}
	putRouter := map[string]ControllerHandler{}
	deleteRouter := map[string]ControllerHandler{}
	core := &Core{
		router: make(map[string]map[string]ControllerHandler, 0),
	}
  // 初始化好四种类型的路由map
	core.router[GET] = getRouter
	core.router[POST] = postRouter
	core.router[PUT] = putRouter
	core.router[DELETE] = deleteRouter
	return core
}
// 注册Get方法
func (c *Core) Get(pattern string, handler ControllerHandler) {
	url := strings.ToUpper(pattern)
	c.router[GET][url] = handler
}
// 注册Post方法
func (c *Core) Post(pattern string, handler ControllerHandler) {
	url := strings.ToUpper(pattern) // 大小写不敏感
	c.router[POST][url] = handler
}
// 注册Put方法
func (c *Core) Put(pattern string, handler ControllerHandler) {
	url := strings.ToUpper(pattern)
	c.router[PUT][url] = handler
}
// 注册Delete方法
func (c *Core) Delete(pattern string, handler ControllerHandler) {
	url := strings.ToUpper(pattern)
	c.router[DELETE][url] = handler
}
// 寻找http+静态路由
func (c *Core) FindRouteByRequest(request *http.Request) ControllerHandler {
	uri := request.URL.Path    //请求处理器映射地址
	method := request.Method   // 请求类型
	upperMethod := strings.ToUpper(method)
	upperURI := strings.ToUpper(uri)
  // 找到类型下的具体地址的映射地址的方法,这里还没有实现动态什么的就固定有1个路径key,但是先别急,后面我们再来动手改造
	if data, ok := c.router[upperMethod]; ok {
		if handler, ok1 := data[upperURI]; ok1 {
			return handler
		}
	}
	return nil
}

framework/group.go

给我们的注册路由,加上分组,用group包装,这样对应我们在使用group时就会对应到不同的请求类型的方法了!并且在这一层给所有的注册地址统一加上group前缀地址!

package framework
//IGroup 代表前缀分组
type IGroup interface {
	Get(string, ControllerHandler)
	Post(string, ControllerHandler)
	Delete(string, ControllerHandler)
	Put(string, ControllerHandler)
}
//
type Group struct {
	core   *Core //
	perfix string // 自身前缀
}
func (g Group) Get(s string, handler ControllerHandler) {
	url := g.perfix + s
	g.core.Get(url, handler)
}
func (g Group) Post(s string, handler ControllerHandler) {
	url := g.perfix + s
	g.core.Post(url, handler)
}
func (g Group) Delete(s string, handler ControllerHandler) {
	url := g.perfix + s
	g.core.Delete(url, handler)
}
func (g Group) Put(s string, handler ControllerHandler) {
	url := g.perfix + s
	g.core.Put(url, handler)
}
func NewGroup(core *Core, perfix string) *Group {
	return &Group{core: core, perfix: perfix}
}
func (c *Core)Group(prefix string)IGroup{
	return NewGroup(c,prefix)
}

如何实现动态路由

首先先定义好我们的动态路由数据结构

// 实现动态路由匹配树
type Tree struct {
	root *node // 根结点
}
// 代表节点
type node struct {
	isLast  bool              // 代表这个节点是否可以成为最终的路由规则。 该节点是否能成为一
	segment string            // url 中的字符串,代表这个节点表示的路由中某个段的字符串
	handler ControllerHandler // 代表这个节点中包含的控制器,用于最终加载调用
	childes []*node           // 代表这个节点下的子节点
}

我们要做的就是在每次注册的时候去将对应的路径的东西将之前的map[string]map[string]ControllerHandler替换为新改造的这个Tree!

从node的结构来看我们应该判断我们的segment去添加我们的childes的node在最后的节点的时候赋值一下处理方法

//matchNode 方法的参数是一个 URI,返回值是指向 node 的指针,它的实现思路是使用函数递归
// 判断是否动态路由
func isWildSegment(segment string) bool {
	return strings.HasPrefix(segment, ":")
}

下面是我们需要的一些功能函数,递归匹配路由和找到下一层的子节点

//过滤下一层满足 segment 规则的子节点
func (n *node) filterChildNodes(segment string) []*node {
   if len(n.childes) == 0 {
      return nil
   }
   // 如果是动态路由则子节点直接满足条件
   if isWildSegment(segment) {
      return n.childes
   }
   // 不是的话就从子节点里面找2
   nodes := make([]*node, 0, len(n.childes))
   for _, node := range n.childes {
      // 判断所有子节点里面是否有动态路由或者唯一匹配的路由
      if isWildSegment(node.segment) || node.segment == segment {
         nodes = append(nodes, node)
      }
   }
   return nodes
}
// 匹配路由
func (n *node) matchNode(url string) *node {
   // 正序拆分路由第一个/
   segments := strings.SplitN(url, "/", 2)
   segment := segments[0] // 第一个路由节点
   //判断如果不是动态路由,那么都统一大写
   if !isWildSegment(segment) {
      segment = strings.ToUpper(segment)
   }
   // 找到下一层路由节点
   nodes := n.filterChildNodes(segment)
   // 错误返回
   if nodes == nil || len(nodes) <= 0 {
      return nil
   }
   //如果只有一个子节点了,是最后的话就返回最后的一个路由节点
   if len(segments) == 1 {
      for _, node := range nodes {
         if node.isLast {
            return node
         }
      }
      return nil
   }
   // 否则持续循环去判断各个节点集合中的递归下一层
   for _, v := range nodes {
      toMatch := v.matchNode(segments[1])
      if toMatch != nil {
         return toMatch
      }
      return nil
   }
   return nil
}

下面是增加路由,以及提供给外部用的,找到对应执行逻辑的控制器方法!

// 增加路由
func (tree *Tree) AddRoute(url string, handler ControllerHandler) error {
	n := tree.root
	// 确认路由是否已存在
	if n.matchNode(url) != nil {
		return errors.New(fmt.Sprintf("add router %v error", url))
	}
	segments := strings.Split(url, "/")
	// 对每个segment
	for index, segment := range segments {
		// 不是动态路由的静态节点 需要转变大写
		if !isWildSegment(segment) {
			segment = strings.ToUpper(segment)
		}
		isLast := index == len(segments)-1 // 判断是否为最后一个节点
		var objNode *node
		childNodes := n.filterChildNodes(segment)
		if len(childNodes) > 0 {
			// 如果有segment相同的子节点,则选择这个子节点
			for _, node := range childNodes {
				if node.segment == segment {
					objNode = node
					break
				}
			}
		}
		// 如果没有找到相同的子节点,那么就自己构造一个添加进tree里面
		if objNode == nil {
			objNode = &node{
				isLast:  isLast,
				segment: segment,
				handler: nil,
				childes: make([]*node, 0),
			}
			if isLast {
				objNode.handler = handler
			}
			n.childes = append(n.childes, objNode)
		}
		n = objNode
	}
	return nil
}
// 寻找对应的映射控制器处理方法
func (tree *Tree) FindHandler(url string) ControllerHandler {
	// 直接复用
	matchNode := tree.root.matchNode(url)
	if matchNode == nil {
		return nil
	}
	return matchNode.handler
}

改造一下core.go

将实现了动态路由的Tree替换进来

package framework
import (
	"log"
	"net/http"
	"strings"
)
const (
	GET    = "GET"
	PUT    = "PUT"
	DELETE = "DELETE"
	POST   = "POST"
)
type Core struct {
	router map[string]*Tree
}
func (c Core) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
	ctx := NewContext(request, writer)
	router := c.FindRouteByRequest(request)
	if router == nil {
		ctx.Json(404, "router not found ")
		return
	}
	if err := router(ctx); err != nil {
		ctx.Json(500, "server Interval")
		return
	}
	//http.DefaultServeMux.ServeHTTP(writer, request)
}
func NewCore() *Core {
	getRouter := NewTree()
	postRouter := NewTree()
	putRouter := NewTree()
	deleteRouter := NewTree()
	core := &Core{
		router: make(map[string]*Tree, 0),
	}
	core.router[GET] = getRouter
	core.router[POST] = postRouter
	core.router[PUT] = putRouter
	core.router[DELETE] = deleteRouter
	return core
}
// 注册Get方法
func (c *Core) Get(pattern string, handler ControllerHandler) {
	url := strings.ToUpper(pattern)
	if err := c.router[GET].AddRoute(url, handler); err != nil {
		log.Fatal("add router error:", err)
	}
}
// 注册Post方法
func (c *Core) Post(pattern string, handler ControllerHandler) {
	url := strings.ToUpper(pattern) // 大小写不敏感
	if err := c.router[POST].AddRoute(url, handler); err != nil {
		log.Fatal("add router error:", err)
	}
}
func (c *Core) Put(pattern string, handler ControllerHandler) {
	url := strings.ToUpper(pattern)
	if err := c.router[PUT].AddRoute(url, handler); err != nil {
		log.Fatal("add router error:", err)
	}
}
func (c *Core) Delete(pattern string, handler ControllerHandler) {
	url := strings.ToUpper(pattern)
	if err := c.router[DELETE].AddRoute(url, handler); err != nil {
		log.Fatal("add router error:", err)
	}
}
// 寻找http+静态路由
func (c *Core) FindRouteByRequest(request *http.Request) ControllerHandler {
	uri := request.URL.Path
	method := request.Method
	upperMethod := strings.ToUpper(method)
	// upperURI := strings.ToUpper(uri)  内部路由会去判断非动态会转大写
	if data, ok := c.router[upperMethod]; ok {
		return data.FindHandler(uri)
	}
	return nil
}

验证

编写两个Controller

func UserLoginController(ctx *framework.Context) error {
	ctx.Json(200, "ok,UserLoginController")
	return nil
}
func SubjectListController(ctx *framework.Context) error {
	ctx.Json(200, "ok,SubjectListController")
	return nil
}

启动运行

到此这篇关于Golang中HTTP路由设计的使用与实现的文章就介绍到这了,更多相关Golang HTTP路由设计内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Golang HTTP编程的源码解析详解

    目录 1.网络基础 2.Golang HTTP编程 2.1 代码示例 2.2 源码分析 3. 总结 1.网络基础 基本TCP客户-服务器程序Socket编程流程如如下图所示. TCP服务器绑定到特定端口并阻塞监听客户端端连接, TCP客户端则通过IP+端口向服务器发起请求,客户-服务器建立连接之后就能开始进行数据传输. Golang的TCP编程也是基于上述流程的. 2.Golang HTTP编程 2.1 代码示例 func timeHandler(w http.ResponseWriter, r

  • Golang Http请求返回结果处理

    在 Go 中 Http 请求的返回结果为 *http.Response 类型,Response.Body 类型为 io.Reader,把请求结果转化为Map需要进行一些处理. 写一个公共方法来进行Response转Map处理: package util import (     "encoding/json"     "net/http"     "io/ioutil" ) func ParseResponse(response *http.Re

  • Golang搭建HTTP服务器

    目录 一. 安装Golang 二. 搭建HTTP服务器 三. 扩展HTTP服务器 处理HTTP请求 处理参数 处理静态文件 四. 总结 一. 安装Golang 在开始之前,我们需要先安装好Golang.你可以在官网下载Golang的安装包,然后安装到你的电脑上.安装好后,通过命令行工具验证Golang是否安装成功,可以输入下面的命令: go version 如果输出Golang的版本号,说明Golang安装成功. 二. 搭建HTTP服务器 下面我们开始搭建HTTP服务器.首先我们需要将下面的代码

  • Golang HTTP服务超时控制实现原理分析

    目录 前情提要 Context 封装自定义的Context context.go main.go Core.go router.go main.go 前情提要 因为上一篇提过,每次来一个请求,然后就会起一个goroutinue那么导致的可能就是一个树形结构的请求图,底下节点在执行中如果发生了超时,那么就有协程会堆积,所以超时控制是有必要的,一般的实现都由一个顶层设计一个Context进行自顶向下传递,这样可以从一个地方去避免多处执行异常,对于Context的过多细节我不在这里一一阐述,有需要的我

  • Golang实现HTTP编程请求和响应

    请求: HTTP 请求报文由请求行.请求头部.空行.请求包体4个部分组成,如下图所示: 请求行: 请求行由方法字段.URL 字段 和HTTP 协议版本字段 3个部分组成,他们之间使用空格隔开.常用的 HTTP 请求方法有 GET.POST. GET: 当客户端要从服务器中读取某个资源时,使用GET 方法.GET 方法要求服务器将URL 定位的资源放在响应报文的数据部分,回送给客户端,即向服务器请求某个资源. 使用GET方法时,请求参数和对应的值附加在 URL 后面,利用一个问号(“?”)代表UR

  • Golang中基于HTTP协议的网络服务

    目录 一.HTTP协议的网络服务 1.1 使用http.Get函数访问HTTP协议的网络服务 1.2 使用缺省客户端DefaultClient(类型为*http.Client ) 1.3 使用http.Client访问HTTP协议的网络服务 二.http.Client中的Transport字段 (1)http.Transport类型中的DialContext字段 (2)http.Transport类型中的其它字段 三.为什么会出现空闲的连接 3.1 空闲连接的产生 3.2 杜绝空闲连接的产生 四

  • Golang实现简单http服务器的示例详解

    目录 一.基本描述 二 .具体方法 2.1 连接的建立 2.2 http请求解析 2.3 http请求处理 2.4 http请求响应 三.完整示例 一.基本描述 完成一个http请求的处理和响应,主要有以下几个步骤: 监听端口 建立连接 解析http请求 处理请求 返回http响应 完成上面几个步骤,便能够实现一个简单的http服务器,完成对基本的http请求的处理 二 .具体方法 2.1 连接的建立 go中net包下有提供Listen和Accept两个方法,可以完成连接的建立,可以简单看下示例

  • 一文带你了解Golang中interface的设计与实现

    目录 前言 接口是什么 iface 和 eface 结构体 _type 是什么 itab 是什么 生成的 itab 是怎么被使用的 itab 关键方法的实现 根据 interfacetype 和 _type 初始化 itab 接口断言过程总览(类型转换的关键) panicdottypeI 与 panicdottypeE iface 和 eface 里面的 data 是怎么来的 convT* 方法 Java 里面的小整数享元模式 总结 在上一篇文章<go interface 基本用法>中,我们了

  • Golang中的路由使用详解

    之前有篇文章比较浅显的分析了一下golang的服务器如何实现,还有Handler, DefaultServeMux,HandlerFunc的用处. 我们现在已经明白了DefaultServeMux就是存放pattern和handler的地方,我们称其为路由,那么我们可能会想,既然golang能够实现这个路由,我们能否也模仿一个呢? 首先我们需要一个能够保存客户端的请求的一个容器(路由). 创建路由结构体 type CopyRouter struct { router map[string]map

  • 详解golang中bufio包的实现原理

    最近用golang写了一个处理文件的脚本,由于其中涉及到了文件读写,开始使用golang中的 io 包,后来发现golang 中提供了一个bufio的包,使用这个包可以大幅提高文件读写的效率,于是在网上搜索同样的文件读写为什么bufio 要比io的读写更快速呢?根据网上的资料和阅读源码,以下来详细解释下bufio的高效如何实现的. bufio 包介绍  bufio包实现了有缓冲的I/O.它包装一个io.Reader或io.Writer接口对象,创建另一个也实现了该接口,且同时还提供了缓冲和一些文

  • 浅谈Golang中创建一个简单的服务器的方法

    我们知道,golang中的net/http包对网络的支持非常好,这样会让我们比较容易的建立起一个相对简单的服务器,我们来看一段代码 func sayHi(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w,"Hi") } func main() { http.HandleFunc("/sayHi", sayHi) log.Fatal(http.ListenAndServe("localhost:80

  • 深入Golang中的sync.Pool详解

    我们通常用golang来构建高并发场景下的应用,但是由于golang内建的GC机制会影响应用的性能,为了减少GC,golang提供了对象重用的机制,也就是sync.Pool对象池. sync.Pool是可伸缩的,并发安全的.其大小仅受限于内存的大小,可以被看作是一个存放可重用对象的值的容器. 设计的目的是存放已经分配的但是暂时不用的对象,在需要用到的时候直接从pool中取. 任何存放区其中的值可以在任何时候被删除而不通知,在高负载下可以动态的扩容,在不活跃时对象池会收缩. sync.Pool首先

  • golang中对"引用传递"的误解

    前情提要 最近看很多教程或者说博客上都说 golang 中的 slice.map.channel.func 都是"引用传递",然而一方面又说 golang 中所有类型都是值传递,总感觉有些云里雾里的,于是我亲自做了下测试和思考. 这里是代码部分: package main import ( "fmt" ) func test(a *int) { fmt.Println("传入变量的值:", a) fmt.Println("传入变量的地址

  • golang beego框架路由ORM增删改查完整案例

    目录 程序运行前加载 路由设置 高级路由设置 beego-ORM初始化 安装ORM+导包 定义结构体 beego支持的数据库 连接数据库 注册数据库表 生成表 完整案例 ORM增删改查 插入 查询 更新 删除 案例 注册 案例2 程序运行前加载 1.导包前面加下划线,运行前加载 2.把要加载的写在init函数里面 路由设置 路由的作用:根据不同的请求指定不同的控制器 路由函数: beego.Router("/path",&controller.MainController{})

  • golang中defer的基本使用教程

    目录 前言 1.什么是defer 2.defer的特点 3.defer什么时间执行 4.defer常见的坑 1.输出是多少? 2.输出多少 3.输出多少 4.输出什么 总结 前言 第一次看go基础语法的时候,用使用到了defer.但是一直不知道它到底是什么,有什么用途.这几天通过查询.学习.算是对defer有了一点浅显的认识. 1.什么是defer defer是go中一种延迟调用机制,defer后面的函数只有在当前函数执行完毕后才能执行,通常用于释放资源. 2.defer的特点 defer遵循先

  • 一文详解Golang中net/http包的实现原理

    目录 前言 http包执行流程 http包源码分析 端口监听 请求解析 路由分配 响应处理 前言 Go语言自带的net/http包提供了HTTP客户端和服务端的实现,实现一个简单的http服务非常容易,其自带了一些列结构和方法来帮助开发者简化HTTP服务开发的相关流程,因此我们不需要依赖任何第三方组件就能构建并启动一个高并发的HTTP服务器,net/http包在编写web应用中有很重要的作用,这篇文章会学习如何用 net/http 自己编写实现一个 HTTP Server 并探究其实现原理,具体

  • Golang 中的 unsafe.Pointer 和 uintptr详解

    目录 前言 uintptr unsafe.Pointer 使用姿势 常规类型互转 Pointer => uintptr 指针算数计算:Pointer => uintptr => Pointer reflect 包中从 uintptr => Ptr 实战案例 string vs []byte sync.Pool 前言 日常开发中经常看到大佬们用各种 unsafe.Pointer, uintptr 搞各种花活,作为小白一看到 unsafe 就发憷,不了解二者的区别和场景,自然心里没数.

随机推荐

其他