写在前面:

一年半前,这个系列就是学长推荐给我的,当时我还是初学 Go 的状态,不过学到一半没有继续下去,最近出于工作原因需要用到 Golang,翻找学习资料的时候忽然发现这个很久之前学的东西,很是喜欢,一年半时间过去,再次回望这个 Gee 系列,心中有了许多新的感悟,之前学的时候糊里糊涂,没有意识到研究这个具有的极大价值,算是朝花夕拾了。


Gee 是一个 类 Gin 的 Web 框架

首先认识到出于开发一个 Web 框架这个目的来说,我们需要开发的是什么

我们是基于标准库 “net/http” 进行进一步开发,net/http 包已提供的能力有监听端口,路由映射(静态),解析/构建 HTTP 包(解析 HTTP Req,构建 HTTP Resp)

那么我们可以进一步开发的功能有很多,比如:动态路由映射(在原有的静态路由升级而来),鉴权,处理 cookie、headers 等,插件功能

那么我们在开发 Web 框架之前,需要熟悉 net/http 的能力边界

今天使用 net/http 实现了最基本的 Web 功能:接收请求并返回。

不过即使是这个最基本的功能,也用到了 net/http 的三种能力:端口监听、路由映射(静态)、解析/构建 HTTP 包

我们在对于 net/http 包的抽象基本能力的了解基础上,进一步熟悉实现这些能力具体的函数(包括它的定义、输入参数、输出等)

端口监听:http.ListenAndServe(addr string, handler Handler)

路由映射(静态):http.HandleFunc(pattern string, handler Func(http.ResponseWriter, *http.Request))

解析/构建 HTTP 包:http.Requesthttp.ResponseWriter 具备这俩功能的东西——“请求处理器” Handler,它本身是一个接口(接口就是用于描述一种东西)

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

整合这些相关函数,我们得到了最简单的 Web 服务

package main

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

func main() {
s := "gopher"
fmt.Printf("Hello and welcome, %s!\n", s)

http.HandleFunc("/", indexHandler)
log.Fatal(http.ListenAndServe(":8000", nil))
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
fmt.Println("index")
}

通过终端执行命令 curl localhost:8000 来进行测试验证,控制台会打印 index

dccc302c7a206d8221eef8182bc05936

bf04e5830d503ee4744813be84ee6ac0

遐思 - 1

能不能导入 net 包,写成 net.ListenAndServe ?

import (
"net"
)

func main() {
net.ListenAndServe(":8080", nil);
}

不行,虽然从文件结构上来说 net 文件夹包含了 http 文件夹,但是从包管理来看,net 包 和 net/http 包毫无关系,相互独立。

疑惑 - 1

func main() {
s := "gopher"
fmt.Printf("Hello and welcome, %s!\n", s)

http.HandleFunc("/", indexHandler)
log.Fatal(http.ListenAndServe(":8000", nil))
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
fmt.Println("index")
}

使用 Ghrome 浏览器 发起一次 localhost:8000 请求进行测试,打印两次 index

浏览器发送请求时会自动发送一个 localhost:8000/favicon.ico,打开开发者工具可以看到该请求

并且因为 “/“ 是通配符,所以两种路径的请求都会触发 indexHandler(通配符另外讨论)

可以通过 curl 命令发送纯净的请求进行测试

遐思 - 2

为什么 http.HandleFunc(pattern string, handler Func(http.ResponseWriter, *http.Request)) 的第二个参数不用 Handler 接口而用了一个类似的 函数,其实没什么深意,或者说有深意,就是偷懒,是一种“语法糖” 标准做法是这样的:func Handle(pattern string, handler Handler) 简化做法:func HandleFunc(pattern string, func(http.ResponseWriter, *http.Request)) (其实关于这点在函数名 Handle 和 HandleFunc 里就可以窥见一二,Go 的发明者对于函数起名还是很讲究的)

因为如果我们要创建一个实现 Handler 接口的对象相对比较繁琐,需要这样写:

func main() {
http.Handle("/", handler{name: "handler-1"})
log.Fatal(http.ListenAndServe(":8000", nil))
}

type handler struct {
name string
}

func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Println("Handle:" + h.name)
}

13b28369512f581df5a6f0222f2e87d0

不过 函数 和 对象 还有其他区别,函数无法存储数据,只能输入输出;而对象可以存储数据,像图中的 name。所以说用对象这种方式的可操作空间更大