Golang个人web框架开发-学习流程

Golang-个人web框架

github仓库

github地址:ameamezhou/golang-web-frame

后续还将继续学习更新

创建github仓库

设置免密登录

ssh-keygen 一路回车就OK 上面有告诉你密钥生成地址

红框为需要上传的公钥

web框架学习

首先明确目标--我们学习开发web框架的目的是

在日常的web开发中,我们经常要使用到web框架,python 就有很多好用的框架,比如flaskdjango ,前者小巧精美,后者厚重却有着齐全的功能,不同开发者在设计框架的时候会有他们不同的看法和理念,因此在不同框架之间就会有许多不同的区别。这对于Go语言来说也是一样的,我们看到有很多好用的框架,例如BeegoGin等等。但是我们在用这些框架的时候,我们可能需要去思考一下,其实这些框架翻找源码到底其实都是http等基础库构成的,但是我们为什么要使用它们呢?我们用框架究竟目的是什么?只有我们想明白了这一点我们才能更好的去做我们的开发工作,因此我决定做一个简单的框架实现这些基础功能。

开发周期

第一阶段--了解

go 复制代码
package main

import (
	"net/http"
)

func main()  {
	http.HandleFunc("/", sayHello)
	http.ListenAndServe("localhost:9999", nil)
}


// 最基础的功能展示, 这里函数携带的参数是根据http库里面定义的
func sayHello(w http.ResponseWriter, r *http.Request){
	w.Write([]byte("hello world"))
}

实现一个最简单的web功能,就是打开页面输出 hello world 这里其实可以看到所需代码量其实和用现存的 gin 或者 beego 框架差不多,这里也能看出一些web框架大概的逻辑

然后我们在里面加点功能,增加点json输出

go 复制代码
package main

import (
	"encoding/json"
	"net/http"
)

func main()  {
	http.HandleFunc("/", sayHello)
	http.ListenAndServe("localhost:9999", nil)
}


// 最基础的功能展示, 这里函数携带的参数是根据http库里面定义的
func sayHello(w http.ResponseWriter, r *http.Request){
	// 在页面输出展示json
	obj := make(map[string]interface{}, 0)
	obj["username"] = "xiawuyue"
	obj["password"] = "xiaoqizhou"

	// 这里是设置response 的响应头
	w.Header().Set("Content-Type", "application/json")
	// 这里是设置响应头的状态码  ok 就是 200
	w.WriteHeader(http.StatusOK)

	encoder := json.NewEncoder(w)

	if err := encoder.Encode(obj); err != nil {
		http.Error(w, err.Error(), 500)
	}

	w.Write([]byte("hello world"))
}

然后我们可以看到页面

还是非常有意思的

第一阶段思考

两个问题

1 这个demo和你常用的框架的区别

2 你觉得这个地方的重点在哪里

附加:

关于web框架 我们都用过flask框架 请问这些框架最底层的运行逻辑是如何?go实现框架的逻辑相比于python如何?

(欢迎评论讨论)

小结

其实这一阶段我们要着重关注http的路由

go 复制代码
package main

import (
	"net/http"
)

func main()  {
	http.HandleFunc("/", sayHello)
	http.ListenAndServe("localhost:9999", nil)
}


// 最基础的功能展示, 这里函数携带的参数是根据http库里面定义的
func sayHello(w http.ResponseWriter, r *http.Request){
	w.Write([]byte("hello world"))
}

在这里我们可以看到http.ListenAndServe 这里我们传进去的是一个nil,在里面是需要绑定路由的,也就是我们最关键的地方在HandleFunc这里,我们可以看到路由分发是通过 http.HandleFunc("路径", 处理函数) 这种形式实现的

go 复制代码
// HandleFunc registers the handler function for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	DefaultServeMux.HandleFunc(pattern, handler)
}

// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	if handler == nil {
		panic("http: nil handler")
	}
	mux.Handle(pattern, HandlerFunc(handler))
}

在ListenAndServe 这个函数中,我们第二个参数为nil,go会为我们分配一个默认的路由,会携带自己的路由结构体 ServeMux

go 复制代码
// ListenAndServe always returns a non-nil error.
func ListenAndServe(addr string, handler Handler) error {
	server := &Server{Addr: addr, Handler: handler}
	return server.ListenAndServe()
}

// ServeMux还负责清除URL请求路径和主机标头,剥离端口号并重定向包含的任何请求。
// 或元素或重复的斜杠转换为等效的、更干净的URL。
type ServeMux struct {
	mu    sync.RWMutex // 这是一个互斥锁,保证并发
	m     map[string]muxEntry // 具体的路由规则
	es    []muxEntry // slice of entries sorted from longest to shortest.
	hosts bool       // whether any patterns contain hostnames 查看是否包含具体的host信息
}

其中我们最需要关注的就是这个m,我们注意到它是一个map类型,是一个 string 对应一个 muxEntry 结构体,这里最重要的就是muxEntry

Handler 其实就是一个interface接口,所以我们每一个HandFunc里面对应函数的类型都是要和这个 ServeHTTP(ResponseWriter, *Request) 保持一致的

go 复制代码
type muxEntry struct {
	h       Handler // 具体路由对应的 handler
	pattern string  // 匹配字符串
}

type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}

题外话,go和python c 等语言不一样,这一块不需要通过sokcet来搞端口监听,http一个包就囊括了这些功能,所以我们可以深挖一下源码,看看究竟是怎么做得这方面的功能

第二阶段

看了上面的源码,我们实现的关键其实就是两个,一个是 ServeMux 一个是 muxEntry ,然后具体的 Handler 其实对应的就是一个ServeHTTP ,我们需要实现的具体功能就是在这一块。所以其实我们完全可以自己来实现一个,不依赖 net/http 库它内置的一些功能,用我们自己的方式写一个 ServeHTTP

我们先梳理下这次的主要思路:

  • base1的重点就是简单了解http库
  • 我们来尝试自己写一个handle
  • 以后我们的所有的框架代码都不再放在main.go 下面 养成包开发的习惯从主函数去调用

根据第一阶段的总结,我们不难发现我们要是想要自己实现一个框架,那么核心就是要实现一个 muxEntryHandler

根据需求我们可以实现:

go 复制代码
package xiawuyue

import (
	"fmt"
	"net/http"
)

/*
本地包用法:
require xiawuyue v0.0.0

replace xiawuyue => ./base2/xiawuyue
*/

type XiaWuYue struct {
	router map[string]HandleFunc
}

// New 直接调用New方法构建对象
func New() *XiaWuYue {
	return &XiaWuYue{ router: make(map[string]HandleFunc) }
}

// HandleFunc 简单定义一类函数  这就是后续具体的处理方法的类型
type HandleFunc func(w http.ResponseWriter, req *http.Request)

func (x *XiaWuYue)addRoute(method string, pattern string, handleFunc HandleFunc) {
	// 其中method 是用来区分 get post 等方法的
	// patter 是提到的 muxEntry 中的匹配字符串 也就是具体的路径
	key := method + "-" + pattern
	x.router[key] = handleFunc
}

func (x *XiaWuYue) Get(pattern string, handleFunc HandleFunc) {
	x.addRoute("GET", pattern, handleFunc)
}

func (x *XiaWuYue) Post(pattern string, handleFunc HandleFunc) {
	x.addRoute("POST", pattern, handleFunc)
}

func (x *XiaWuYue) Pull(pattern string, handleFunc HandleFunc) {
	x.addRoute("PULL", pattern, handleFunc)
}

func (x *XiaWuYue) Delete(pattern string, handleFunc HandleFunc) {
	x.addRoute("DELETE", pattern, handleFunc)
}

func (x *XiaWuYue) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	switch req.URL.Path {
	case "/":
		fmt.Println("你访问的是根路径")
		w.Write([]byte("hello world"))
		// 这里会导致只在终端打印  所以要修改逻辑
	}

	key := req.Method + "-" + req.URL.Path
	if handler, ok := x.router[key]; ok {
		handler(w, req)
	}
	
}

这里Handler其实就是要求一个接口,这个接口它必须有 ServerHTTP 这个功能就ok,只要能理解这个,做这个逻辑的时候就会很清晰了,我们要实现的就是它的基本功能,并通过 ServerHTTP 对找到的路由提供相应的服务就行,所以这里我们新生成的 struct xiawuyue 它就需要带有这个功能接口

好的 代码看到这里我们来回忆一下第一天的内容:

go 复制代码
package main

import (
	"net/http"
)

func main()  {
	http.HandleFunc("/", sayHello)
	http.ListenAndServe("localhost:9999", nil)
}


// 最基础的功能展示, 这里函数携带的参数是根据http库里面定义的
func sayHello(w http.ResponseWriter, r *http.Request){
	w.Write([]byte("hello world"))
}

这里我们看到原始的 HandleFunc 我们并没有初始化任何一个struct 对象,并且在 ListenAndServe 这里传进去的也是个 nil , 这里的逻辑究竟是怎样的,我们为什么这样也能够去正常跑一个服务?

go 复制代码
// serverHandler delegates to either the server's Handler or
// DefaultServeMux and also handles "OPTIONS *" requests.
type serverHandler struct {
	srv *Server
}

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
	handler := sh.srv.Handler
	if handler == nil {
		handler = DefaultServeMux
	}
	if req.RequestURI == "*" && req.Method == "OPTIONS" {
		handler = globalOptionsHandler{}
	}

	if req.URL != nil && strings.Contains(req.URL.RawQuery, ";") {
		var allowQuerySemicolonsInUse int32
		req = req.WithContext(context.WithValue(req.Context(), silenceSemWarnContextKey, func() {
			atomic.StoreInt32(&allowQuerySemicolonsInUse, 1)
		}))
		defer func() {
			if atomic.LoadInt32(&allowQuerySemicolonsInUse) == 0 {
				sh.srv.logf("http: URL query contains semicolon, which is no longer a supported separator; parts of the query may be stripped when parsed; see golang.org/issue/25192")
			}
		}()
	}

	handler.ServeHTTP(rw, req)
}

我们可以在 http 包的server.go 中找到这样一段,这里其实很好理解,当 svrhandlernil 的时候,我们就会将 DefaultServeMux 导入当作这个 muxEntry ,它拥有 ServerHTTP 这个接口 可以实现相应的功能。

在我们都理解了这一块的知识之后,就写个总的 main.go 函数进行调用就ok

go 复制代码
package main

import (
	"fmt"
	"net/http"
	"xiawuyue/base2/xiawuyue"
)

// base1的重点就是简单了解http库
// 我们来尝试自己写一个handle

// 以后我们的所有的框架代码都不再放在main.go 下面  养成包开发的习惯
// 从主函数去调用

//func main(){
//	http.ListenAndServe(":9999", new(xiawuyue.XiaWuYue))
//}

func main()  {
	//如果没有 New 方法
	//r := new(xiawuyue.XiaWuYue)
	r := xiawuyue.New()
	r.Get("/get", func(w http.ResponseWriter, req *http.Request) {
		fmt.Println("hello world")
		w.Write([]byte("hello world get"))
		// 这里println 只会在终端输出  所以我们后续还是要包装一个w.return 的功能,其实很简单
	})
	// 请大家给斗鱼9999fg投一票 球球了
	http.ListenAndServe("localhost:9999", r)
	// 到这一步完成了然后就去启动
}

第三阶段

相关推荐
长栎10 小时前
写 for 循环写了十年,你却从没用过迭代器模式最狠的那一面
后端
LiaCode10 小时前
Redis 在生产项目的使用
前端·后端
用户5598224812210 小时前
Docker Compose Down 导致容器数据误删——ext4 日志恢复全记录
后端
LiaCode10 小时前
一天学完 redis 的爽翻版核心知识总结
前端·后端
大刚测试开发实战10 小时前
如何内网穿透访问本地私有化部署的TestHub
前端·后端·github
xiaodaoluanzha11 小时前
迄今為止,最簡單的編程語言 Nolang
前端·后端
Csvn11 小时前
Docker 容器管理入门 — 从镜像到容器编排
后端
用户7623524259111 小时前
ShardingJDBC
后端
行者全栈架构师11 小时前
IDEA 中 Maven 项目的 15 个红色报错快速解决方法
java·后端
Colin草率地做慢慢地改11 小时前
关于QuickStore这个项目的重构(2)- 数据库建表文件
后端·面试·架构