7天用Go从零实现Web框架Gee教程(1)

本文参考:https://geektutu.com/post/gee.html

研究这个教程的时候,我刚刚开始学习用Go,觉得单纯看语法学习效率较低,而且容易遗忘,所以就打算边看边学,顺便学习Web框架相关的知识,之前用过爬虫,但还只是浅尝辄止,稀里糊涂,我想学习Web框架也有助于对爬虫的理解。

对于初步使用Go语言搭建Web应用的人来说,Gin框架无疑是最常见的,我也不例外,之前也用Gin写了一个简单的TODOList, 而Gee框架是一个简化的类Gin框架,适合帮助初学者理解Web框架。

写这篇博客的时候我已经看到了Day3,到Day3的时候项目已经逐渐变得稍微复杂起来,而且出现了单测这个之前有所耳闻的概念,脑子里有些混乱了,于是打算停下来复盘一下,顺便写篇博客。

Day0 序言

框架有简化开发的作用,避免了不必要的繁复操作,同时框架提供了很多额外的功能,像是动态路由、中间件等等

Day1 HTTP基础

这篇包括了三个Web应用版本,由最基础逐渐复杂起来(虽然整个教程就是这样一个常见的“合理的”难度梯度)

base1

这里主要就是要了解net/http标准库中的HandleFunc和ListenAndServe两个函数

// day1-http-base/base1/main.go
package main

import (
"fmt"
"log"
"net/http"
)

func main() {
http.HandleFunc("/", indexHandler)
http.HandleFunc("/hello", helloHandler)
log.Fatal(http.ListenAndServe(":9999", nil))
}

// handler echoes r.URL.Path
func indexHandler(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "URL.Path = %q\n", req.URL.Path)
}

// handler echoes r.URL.Header
func helloHandler(w http.ResponseWriter, req *http.Request) {
for k, v := range req.Header {
fmt.Fprintf(w, "Header[%q] = %q\n", k, v)
}
}

ListenAndServe这个函数是比较简单的,看看ListenAndServe的源码好了

func ListenAndServe(addr string, handler Handler) error {  
// 创建一个 TCP 监听器
listener, err := net.Listen("tcp", addr)
if err != nil {
return err
}
// 确保在退出时关闭监听器
defer listener.Close()

// 如果没有提供处理器,使用 DefaultServeMux
if handler == nil {
handler = DefaultServeMux
}

// 进入接受连接的循环
for {
// 接受连接
conn, err := listener.Accept()
if err != nil {
// 处理错误
continue
}

// 为每个连接启用一个新的 goroutine
go func() {
defer conn.Close() // 确保连接在处理完后关闭
// 处理请求的逻辑
// 这包括读请求、调用处理器、写回响应等
handler.ServeHTTP(conn, ...)
}()
}
}

第一个参数是监听的端口号,第二个参数则是处理器Handler,Handler当然也是http库中定义的接口,需要实现ServeHTTP方法,按我的理解就是ListenAndServe是用来监听指定端口并且指定对应的处理器,(我的博客里会有很多自己的臆测,请自行判断,也欢迎大佬斧正)如果第二个参数是nil则会调用默认的,去看HandleFunc函数

那接下来就看一看HandleFunc函数,先上源码

package http  

// HandleFunc registers the handler function for the URL path prefix.
// The handler will be called for all requests whose URL path starts with
// the given prefix.
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
// Wrap the handler function to convert it to the Handler interface
Handle(pattern, HandlerFunc(handler))
}

// HandlerFunc is an adapter to allow the use of ordinary functions as HTTP handlers.
type HandlerFunc func(ResponseWriter, *Request)

// ServeHTTP calls h(w, r).
func (h HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
h(w, r)
}

// Handle registers the handler h for the given pattern in the DefaultServeMux.
func Handle(pattern string, handler Handler) {
// Check for nil handler
if handler == nil {
panic("http: nil handler")
}
// Register the handler in the DefaultServeMux
DefaultServeMux.Handle(pattern, handler)
}

// Handler is an interface that wraps the ServeHTTP method.
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}

HandleFunc函数就是将URL路由映射到对应的处理方法,处理方法要实现Handler(处理者)接口(这也是很自然的吧)

总结一下,ListenAndServe监听,HandleFunc映射路由,Handler处理。

base2

上面的base1只是最基本最基本的实现方法,base2主要是要创建一个实例,并且实现Handler接口,放在ListenAndServe函数中的第二个参数的位置上

// day1-http-base/base2/main.go
package main

import (
"fmt"
"log"
"net/http"
)

// Engine is the uni handler for all requests
type Engine struct{}

func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
switch req.URL.Path {
case "/":
fmt.Fprintf(w, "URL.Path = %q\n", req.URL.Path)
case "/hello":
for k, v := range req.Header {
fmt.Fprintf(w, "Header[%q] = %q\n", k, v)
}
default:
fmt.Fprintf(w, "404 NOT FOUND: %s\n", req.URL)
}
}

func main() {
engine := new(Engine)
log.Fatal(http.ListenAndServe(":9999", engine))
}

定义Engine结构体,实现ServeHTTP方法,实现的方式是完全自定义的,也就是说,这里有着更大的开发空间,相当于重构了HandleFunc函数。我们利用engine拦截所有的HTTP请求,然后自定义相应方式,包括路由的对应关系也是,对于之前的HandleFunc函数,只能实现具体的路由与处理方法的一一映射,也就是静态路由,而自定义的话就可以实现动态路由,动态路由简单来说就是实现模糊匹配,一个通式路由可以匹配一系列路由。

base3

到base3就初具框架雏形了,base3将构建的东西封装起来,封成一个独立的包,开发者只需调用包中的函数简化开发

由于代码有点开始多了,在放在这里会比较占篇幅,我不放在这了,想看源码可以移步至教程原文,链接在开头

以下是基本框架

gee/
|--gee.go
|--go.mod
main.go
go.mod

Engine中多了一个路由匹配表router的东西,顾名思义,将路由映射到处理方法,这与之前的有什么不同呢?这里增加了GET、POST的概念,也就是说,路由并不是原始的url,而是增加了方法method(大概是GET-/hello这样的感觉),这将请求进行了分类,扩大了请求的概念范围,同时可以实现在同一个页面发送不同的请求的效果

总结一下,Engine结构体,内部有路由匹配表,增强了Handler的功能