http库
实现一个最简单的 http server
需要几行代码? 对于python
可能只需一行,对于 node
可能也要不了几行,那对于 golang
要几行?同样也要不了几行,这几乎是所有现代化高级语言的特性,提供了官方内置库,大大简化了开发工作量
http库就是做这个的,下面看瞎官方解释
Package http provides HTTP client and server implementations.
Get, Head, Post, and PostForm make HTTP (or HTTPS) requests:
最简单的http Server 实现
那说了这么多我们用代码实现下
新建 main.go
文件
package main // 声明 main 包,表明当前是一个可执行程序
import (
"fmt"
"net/http"
)
func home(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "这是主页")
}
func main() { // main函数,是程序执行的入口
//路由注册
http.HandleFunc("/", home)
// 端口监听
http.ListenAndServe("127.0.0.1:8080", nil)
}
使用
go run main.go
启动,会监听8080端口
访问,这里用postman 测试
可以看到,已经可以访问了
http 库 —— Request 概览
- Body 和 GetBody
- URL
- Method
- Header
- Form
- ...
可以看到方法很多,具体方法的用法,有的猜方法名也能猜到,当然具体用法示例可以看官方文档
https://pkg.go.dev/net/http#Request
中文的
https://studygolang.com/pkgdoc
http 库 —— Request Body
Body:只能读取一次,意味着你读了别人 就不能读了;别人读了你就不能读了,并且再次读取也不会报错,只是什么也读不到
用下面这个例子可以测试出来
package main // 声明 main 包,表明当前是一个可执行程序
import (
"fmt"
"io"
"net/http"
)
func readBodyOnce(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
fmt.Fprintf(w, "body 读取失败: %v", err)
// 记住要返回,不然就还会执行后面的代码
return
}
// 类型转换,将 []byte 转换为 string
fmt.Fprintf(w, "读取数据: %s \n", string(body))
// 尝试再次读取,哈也读不到,但是也不会报错
body, err = io.ReadAll(r.Body)
if err != nil {
// 不会进来这里
fmt.Fprintf(w, "再次读取数据出现错误: %v", err)
return
}
fmt.Fprintf(w, "再读一次数据: [%s] 读取数据长度 %d \n", string(body), len(body))
}
func main() { // main函数,是程序执行的入口
http.HandleFunc("/", readBodyOnce)
http.ListenAndServe("127.0.0.1:8080", nil)
}
http 库 —— Request Body - GetBody
- Body:只能读取一次,意味着你读了别人就不能读了;别人读了你就不能读了;
- GetBody:原则上是可以多次读取,但是在原生的http.Request里面,这个是 nil,就是没设置,框架一般都设置好了
- 在读取到 body 之后,我们就可以用于反序列化,比如说将json格式的字符串转化为一个对象等
package main // 声明 main 包,表明当前是一个可执行程序
import (
"fmt"
"net/http"
)
func getBodyIsNil(w http.ResponseWriter, r *http.Request) {
if r.GetBody == nil {
fmt.Fprint(w, "GetBody is nil \n")
} else {
fmt.Fprintf(w, "GetBody not nil \n")
}
}
func main() { // main函数,是程序执行的入口
http.HandleFunc("/", getBodyIsNil)
http.ListenAndServe("127.0.0.1:8080", nil)
}
http 库 —— Request Query
- 除了 Body,我们还可能传递参数的地方是 Query
- 也就是 http://xxx.com/your/path?id=123&b=456
- 所有的值都被解释为字符串,所以需要自己解析为数字等
例
package main // 声明 main 包,表明当前是一个可执行程序
import (
"fmt"
"net/http"
)
func queryParams(w http.ResponseWriter, r *http.Request) {
values := r.URL.Query()
fmt.Fprintf(w, "query is %v\n", values)
}
func main() { // main函数,是程序执行的入口
http.HandleFunc("/", queryParams)
http.ListenAndServe("127.0.0.1:8080", nil)
}
http 库 —— Request URL
包含路径方面的所有信息和一些很有用的 操作
需要注意的是
- URL 里面 Host 不一定有值
- r.Host 一般都有值,是Host这个header的值
- RawPath 也是不一定有
- Path肯定有
例
package main // 声明 main 包,表明当前是一个可执行程序
import (
"encoding/json"
"fmt"
"net/http"
)
func wholeUrl(w http.ResponseWriter, r *http.Request) {
data, _ := json.Marshal(r.URL)
fmt.Fprintf(w, string(data))
}
func main() { // main函数,是程序执行的入口
http.HandleFunc("/", wholeUrl)
http.ListenAndServe("127.0.0.1:8080", nil)
}
所以实际中记得自己输出来看一下,确认有没有
http 库 —— Request Header
-
header大体上是两类:
一类是http 预定义的
一类是自己定义的
-
Go 会自动将 header 名字转为标准名字(其实就是大小写调整
-
一般用 X 开头来表明是自己定义的,比如说 X-mycompanyyour=header
例
package main // 声明 main 包,表明当前是一个可执行程序
import (
"fmt"
"net/http"
)
func hander(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "hender is %v \n", r.Header)
}
func main() { // main函数,是程序执行的入口
http.HandleFunc("/", hander)
http.ListenAndServe("127.0.0.1:8080", nil)
}
http 库 —— Form
- Form 和 ParseForm
- 要先调用 ParseForm
- 建议加上 Content-Type:application/x-www-formurlencoded
form-data、x-www-form-urlencoded 的区别
https://www.cnblogs.com/wbl001/p/12050751.html
form-data与x-www-form-urlencoded的区别
- multipart/form-data:可以上传文件或者键值对,最后都会转化为一条消息
- x-www-form-urlencoded:只能上传键值对,而且键值对都是通过&间隔分开的
post 方式这个方法只能 解析到 x-www-form-urlencoded
方式的,如果是get 方式 参数也可以解析到但是不建议这样用
例:
package main // 声明 main 包,表明当前是一个可执行程序
import (
"fmt"
"net/http"
)
func form(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "before parse form %v\n", r.Form)
err := r.ParseForm()
if err != nil {
fmt.Fprintf(w, "parse form error %v\n", r.Form)
}
fmt.Fprintf(w, "before parse form %v\n", r.Form)
}
func main() { // main函数,是程序执行的入口
http.HandleFunc("/", form)
http.ListenAndServe("127.0.0.1:8080", nil)
}
form-data 方式
x-www-form-urlencoded 方式
http 库使用:要点总结
- Body 和 GetBody:重点在于 Body 是一次性的,而 GetBody 默认情况下是没有,一般中间件会考虑帮你注入这个方法
- URL:注意 URL 里面的字段的含义可能并不如你期望的那样
- Form:记得调用前先用 ParseForm,别忘了请求里面加上 http 头
手撸一个简单的架子
初始化:基于http库,建立一个路由
package main
import (
"fmt"
"net/http"
)
func home(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "这是主页")
}
func main() {
http.HandleFunc("/", home)
http.ListenAndServe("127.0.0.1:8080", nil)
}
至此路由创建完毕
抽象出路由注册和服务启动方法
package main
import (
"fmt"
"net/http"
)
// 抽象接口
type Server interface {
Route(pattern string, handlerFunc http.HandlerFunc)
Start(address string) error
}
// 结构体,相当于实现类
type sdkHttpServer struct {
Name string
}
// 实现接口方法
func (s *sdkHttpServer) Route(pattern string, handlerFunc http.HandlerFunc) {
http.HandleFunc(pattern, handlerFunc)
}
func (s *sdkHttpServer) Start(address string) error {
return http.ListenAndServe(address, nil)
}
// 暴露给外部使用,返回一个 Server 结构体(其实就是实例)
func NewSdkHttpServer(name string) Server {
// 由于引用类型,所以实际是返回 指针
return &sdkHttpServer{
Name: name,
}
}
func home(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "这是主页")
}
func main() {
// 原生
// http.HandleFunc("/", home)
// http.ListenAndServe("127.0.0.1:8080", nil)
// 自己封装
server := NewSdkHttpServer("my-server")
// 注册路由
server.Route("/", home)
// 启动服务
server.Start("127.0.0.1:8080")
}
到这里我们封装完了,这里你们可能会说,你这不就是在原生的基础上套了一层改了个方法名吗?
别急还没完
基于原生实现简单的逻辑注册
我们首先基于原生的http库,实现一个简单注册,不涉及业务
原生注册
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
)
// 抽象接口
type Server interface {
Route(pattern string, handlerFunc http.HandlerFunc)
Start(address string) error
}
// 结构体,相当于实现类
type sdkHttpServer struct {
Name string
}
// 实现接口方法
func (s *sdkHttpServer) Route(pattern string, handlerFunc http.HandlerFunc) {
http.HandleFunc(pattern, handlerFunc)
}
func (s *sdkHttpServer) Start(address string) error {
return http.ListenAndServe(address, nil)
}
// 暴露给外部使用,返回一个 Server 结构体(其实就是实例)
func NewSdkHttpServer(name string) Server {
// 由于引用类型,所以实际是返回 指针
return &sdkHttpServer{
Name: name,
}
}
// 登录信息结构体
type signUpReq struct {
// 导出json字符串中key名字与结构体变量命名一致,
// 我们可以通过结构体定义时指定导出后key值;并且支持如果结构体中变量为空时不导出
Email string `json:"email"`
Password string `json:"password"`
ConfirmedPassword string `json:"confirmed_password"`
}
// 返回信息结构
type commonResponse struct {
BizCode int `json:"biz_code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
// 简单注册
func SignUp(w http.ResponseWriter, r *http.Request) {
// 初始化
req := &signUpReq{}
// 读取body信息
body, err := io.ReadAll(r.Body)
if err != nil {
fmt.Fprintf(w, "read body failed: %v", err)
// 要返回掉,不然就会继续执行后面的代码
return
}
// 将body参数json信息 解析(反序列化)到req 结构体
err = json.Unmarshal(body, req)
if err != nil {
fmt.Fprintf(w, "Unmarshal failed: %v", err)
// 要返回掉,不然就会继续执行后面的代码
return
}
// 得到 req 请求参数结构体 ,处理业务逻辑,等等...
// 初始化返回信息结构体
resp := &commonResponse{
BizCode: 200,
Msg: "正常返回",
Data: 10086,
}
// 序列化结构体 转为 json 串
respJson, err := json.Marshal(resp)
if err != nil {
fmt.Fprintf(w, "Marshal failed: %v", err)
// 要返回掉,不然就会继续执行后面的代码
return
}
// 返回响应
fmt.Fprintf(w, string(respJson))
}
func main() {
// 自己封装
server := NewSdkHttpServer("my-server")
// 注册路由
server.Route("/SignUp", SignUp)
// 启动服务
server.Start("127.0.0.1:8080")
}
请求
127.0.0.1:8080/SignUp
{
"email": "makalo@qq.com",
"password": "password",
"confirmed_password": "confirmed_password"
}
响应
如下图正常返回
原生的缺点
如下图
你们有没有发现,这一块儿的代码,但凡你要读取和输出json,就得来一遍,那么有没有一个东西可以直接帮我自动序列化和反序列化呢?
如果接触过其他语言的框架,或者框架的话,你就会发现几乎所有的扩展,都有这个功能,比如node
koa
不管是数组还是对象,请求过来 参数自动解析 ,响应输出自动转json
这是啥呢?
就是 上下文 对象 Context
对应的就是一次http
请求
上下文对象 Context -> json 转换
实现
例:
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
)
// 抽象接口
type Server interface {
Route(pattern string, handlerFunc http.HandlerFunc)
Start(address string) error
}
// 结构体,相当于实现类
type sdkHttpServer struct {
Name string
}
// 实现接口方法
func (s *sdkHttpServer) Route(pattern string, handlerFunc http.HandlerFunc) {
http.HandleFunc(pattern, handlerFunc)
}
func (s *sdkHttpServer) Start(address string) error {
return http.ListenAndServe(address, nil)
}
// 暴露给外部使用,返回一个 Server 结构体(其实就是实例)
func NewSdkHttpServer(name string) Server {
// 由于引用类型,所以实际是返回 指针
return &sdkHttpServer{
Name: name,
}
}
// 上下文结构体
type Context struct {
W http.ResponseWriter
R *http.Request
}
// 读取并反序列化
// req interface{} 接受任何类型
func (c *Context) ReadJson(req interface{}) error {
// 读取body信息
r := c.R
body, err := io.ReadAll(r.Body)
if err != nil {
// 要返回掉,不然就会继续执行后面的代码
return err
}
// 将body参数json信息 解析(反序列化)到req 结构体
err = json.Unmarshal(body, req)
if err != nil {
// 要返回掉,不然就会继续执行后面的代码
return err
}
return nil
}
// 响应
func (c *Context) WriteJson(code int, resp interface{}) error {
c.W.WriteHeader(code)
// 将响应结构体输入到浏览器
respJson, err := json.Marshal(resp)
if err != nil {
return err
}
_, err = c.W.Write(respJson)
return err
}
// 登录信息结构体
type signUpReq struct {
// 导出json字符串中key名字与结构体变量命名一致,
// 我们可以通过结构体定义时指定导出后key值;并且支持如果结构体中变量为空时不导出
Email string `json:"email"`
Password string `json:"password"`
ConfirmedPassword string `json:"confirmed_password"`
}
// 返回信息结构
type commonResponse struct {
BizCode int `json:"biz_code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
// 简单注册
func SignUp(w http.ResponseWriter, r *http.Request) {
// 使用上下文对象
ctx := &Context{
W: w,
R: r,
}
// 初始化
req := &signUpReq{}
// 传入指针
err := ctx.ReadJson(req)
if err != nil {
fmt.Fprintf(w, "err: %v", err)
// 要返回掉,不然就会继续执行后面的代码
return
}
// 反序列化之后的 req 结构体
fmt.Printf("反序列化之后的 req: %#v \n", req)
// 得到 req 请求参数结构体 ,处理业务逻辑,等等...
// 初始化返回信息结构体
resp := &commonResponse{
BizCode: 200,
Msg: "正常返回",
Data: 10086,
}
err = ctx.WriteJson(http.StatusOK, resp)
if err != nil {
fmt.Fprintf(w, "写入响应失败: %v", err)
}
}
func main() {
// 自己封装
server := NewSdkHttpServer("my-server")
// 注册路由
server.Route("/SignUp", SignUp)
// 启动服务
server.Start("127.0.0.1:8080")
}
请求
127.0.0.1:8080/SignUp
{
"email": "makalo@qq.com",
"password": "password",
"confirmed_password": "confirmed_password"
}
响应
上下文对象 Context -> 常用状态码
通过上面代码,我们发现每次都要传入状态码,其实我们最常用状态码就两种
- 200
- 500
相关状态码查询文档
https://studygolang.com/pkgdoc
那么我们可以不用传入状态码
实现
例:
// 200
func (c *Context) OkJson(resp interface{}) error {
return c.WriteJson(http.StatusOK, resp)
}
// 500
func (c *Context) ErrorJson(resp interface{}) error {
return c.WriteJson(http.StatusInternalServerError, resp)
}
完整代码
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
)
// 抽象接口
type Server interface {
Route(pattern string, handlerFunc http.HandlerFunc)
Start(address string) error
}
// 结构体,相当于实现类
type sdkHttpServer struct {
Name string
}
// 实现接口方法
func (s *sdkHttpServer) Route(pattern string, handlerFunc http.HandlerFunc) {
http.HandleFunc(pattern, handlerFunc)
}
func (s *sdkHttpServer) Start(address string) error {
return http.ListenAndServe(address, nil)
}
// 暴露给外部使用,返回一个 Server 结构体(其实就是实例)
func NewSdkHttpServer(name string) Server {
// 由于引用类型,所以实际是返回 指针
return &sdkHttpServer{
Name: name,
}
}
// 上下文结构体
type Context struct {
W http.ResponseWriter
R *http.Request
}
// 读取并反序列化
// req interface{} 接受任何类型
func (c *Context) ReadJson(req interface{}) error {
// 读取body信息
r := c.R
body, err := io.ReadAll(r.Body)
if err != nil {
// 要返回掉,不然就会继续执行后面的代码
return err
}
// 将body参数json信息 解析(反序列化)到req 结构体
err = json.Unmarshal(body, req)
if err != nil {
// 要返回掉,不然就会继续执行后面的代码
return err
}
return nil
}
// 响应
func (c *Context) WriteJson(code int, resp interface{}) error {
c.W.WriteHeader(code)
// 将响应结构体输入到浏览器
respJson, err := json.Marshal(resp)
if err != nil {
return err
}
_, err = c.W.Write(respJson)
return err
}
// 200
func (c *Context) OkJson(resp interface{}) error {
return c.WriteJson(http.StatusOK, resp)
}
// 500
func (c *Context) ErrorJson(resp interface{}) error {
return c.WriteJson(http.StatusInternalServerError, resp)
}
// 登录信息结构体
type signUpReq struct {
// 导出json字符串中key名字与结构体变量命名一致,
// 我们可以通过结构体定义时指定导出后key值;并且支持如果结构体中变量为空时不导出
Email string `json:"email"`
Password string `json:"password"`
ConfirmedPassword string `json:"confirmed_password"`
}
// 返回信息结构
type commonResponse struct {
BizCode int `json:"biz_code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
// 简单注册
func SignUp(w http.ResponseWriter, r *http.Request) {
// 使用上下文对象
ctx := &Context{
W: w,
R: r,
}
// 初始化
req := &signUpReq{}
// 传入指针
err := ctx.ReadJson(req)
if err != nil {
fmt.Fprintf(w, "err: %v", err)
// 要返回掉,不然就会继续执行后面的代码
return
}
// 反序列化之后的 req 结构体
fmt.Printf("反序列化之后的 req: %#v \n", req)
// 得到 req 请求参数结构体 ,处理业务逻辑,等等...
// 初始化返回信息结构体
resp := &commonResponse{
BizCode: 200,
Msg: "正常返回",
Data: 10086,
}
// err = ctx.WriteJson(http.StatusOK, resp)
// 不用传入状态码了
err = ctx.OkJson(resp)
if err != nil {
fmt.Fprintf(w, "写入响应失败: %v", err)
}
}
func main() {
// 自己封装
server := NewSdkHttpServer("my-server")
// 注册路由
server.Route("/SignUp", SignUp)
// 启动服务
server.Start("127.0.0.1:8080")
}
这样我们就不用传入状态码了
让框架去创建 Contex
如果有使用框架经验的大佬,都知道 Contex 这个对象一般都不是我们业务部分创建的
一般都是 web 框架来创建,有什么好处呢?
框架来创建context,就可以完全控制什么时候 创建,context可以有什么字段。作为设计者, 这种东西不能交给用户自由发挥。
我们希望 业务 部分是这样
// func SignUp(w http.ResponseWriter, r *http.Request) {
// 业务部分直接使用 Context
func SignUp(ctx *Context) {
实现
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
)
// 抽象接口
type Server interface {
Route(pattern string, handlerFunc func(ctx *Context))
Start(address string) error
}
// 结构体,相当于实现类
type sdkHttpServer struct {
Name string
}
// 上下文结构体
type Context struct {
W http.ResponseWriter
R *http.Request
}
// 对外提供创建的方法
func NewContext(writer http.ResponseWriter, request *http.Request) *Context {
return &Context{
R: request,
W: writer,
}
}
// 实现接口方法
// 传入pattern 为路由,handlerFunc 参数为方法,handlerFunc 就是对应传入的方法名
func (s *sdkHttpServer) Route(pattern string, handlerFunc func(ctx *Context)) {
// 闭包创建
http.HandleFunc(pattern, func(writer http.ResponseWriter,
request *http.Request) {
ctx := NewContext(writer, request)
// 调用对应的 handlerFunc 方法, 将 ctx 传入
handlerFunc(ctx)
})
}
func (s *sdkHttpServer) Start(address string) error {
return http.ListenAndServe(address, nil)
}
// 暴露给外部使用,返回一个 Server 结构体(其实就是实例)
func NewSdkHttpServer(name string) Server {
// 由于引用类型,所以实际是返回 指针
return &sdkHttpServer{
Name: name,
}
}
// 读取并反序列化
// req interface{} 接受任何类型
func (c *Context) ReadJson(req interface{}) error {
// 读取body信息
r := c.R
body, err := io.ReadAll(r.Body)
if err != nil {
// 要返回掉,不然就会继续执行后面的代码
return err
}
// 将body参数json信息 解析(反序列化)到req 结构体
err = json.Unmarshal(body, req)
if err != nil {
// 要返回掉,不然就会继续执行后面的代码
return err
}
return nil
}
// 响应
func (c *Context) WriteJson(code int, resp interface{}) error {
c.W.WriteHeader(code)
// 将响应结构体输入到浏览器
respJson, err := json.Marshal(resp)
if err != nil {
return err
}
_, err = c.W.Write(respJson)
return err
}
// 200
func (c *Context) OkJson(resp interface{}) error {
return c.WriteJson(http.StatusOK, resp)
}
// 500
func (c *Context) ErrorJson(resp interface{}) error {
return c.WriteJson(http.StatusInternalServerError, resp)
}
// 登录信息结构体
type signUpReq struct {
// 导出json字符串中key名字与结构体变量命名一致,
// 我们可以通过结构体定义时指定导出后key值;并且支持如果结构体中变量为空时不导出
Email string `json:"email"`
Password string `json:"password"`
ConfirmedPassword string `json:"confirmed_password"`
}
// 返回信息结构
type commonResponse struct {
BizCode int `json:"biz_code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
// 简单注册
// func SignUp(w http.ResponseWriter, r *http.Request) {
func SignUp(ctx *Context) {
// 初始化
req := &signUpReq{}
// 传入指针
err := ctx.ReadJson(req)
if err != nil {
fmt.Fprintf(ctx.W, "err: %v", err)
// 要返回掉,不然就会继续执行后面的代码
return
}
// 反序列化之后的 req 结构体
fmt.Printf("反序列化之后的 req: %#v \n", req)
// 得到 req 请求参数结构体 ,处理业务逻辑,等等...
// 初始化返回信息结构体
resp := &commonResponse{
BizCode: 200,
Msg: "正常返回",
Data: 10086,
}
// err = ctx.WriteJson(http.StatusOK, resp)
// 不用传入状态码了
err = ctx.OkJson(resp)
if err != nil {
fmt.Fprintf(ctx.W, "写入响应失败: %v", err)
}
}
func main() {
// 自己封装
server := NewSdkHttpServer("my-server")
// 注册路由
server.Route("/SignUp", SignUp)
// 启动服务
server.Start("127.0.0.1:8080")
}
支持 RESTFul API
有没有发现大部分框架都是支持,RESTFul API 呢?
那我们怎么实现?
定义
实现
这里我们简单理解,就是要让我们这个简单的框架支持 method 方法
那我们的路由接口必然要增加一个 method 参数
type Server interface {
Route(method string, pattern string, handlerFunc func(ctx *Context))
Start(address string) error
}
对应的实现也要增加method
// 实现接口方法
// 传入method 为访问方式,pattern 为路由,handlerFunc 参数为方法,handlerFunc 就是对应传入的方法名
func (s *sdkHttpServer) Route(method string, pattern string, handlerFunc func(ctx *Context)) {
// 闭包创建
http.HandleFunc(pattern, func(writer http.ResponseWriter,
request *http.Request) {
ctx := NewContext(writer, request)
// 调用对应的 handlerFunc 方法, 将 ctx 传入
handlerFunc(ctx)
})
}
Handler 抽象-自己实现路由路
我们先看看http.ListenAndServe
这个方法,之前我们一直传入的 是 nil
,这个参数到底有啥用?
// ListenAndServe listens on the TCP network address addr and then calls
// Serve with handler to handle requests on incoming connections.
// Accepted connections are configured to enable TCP keep-alives.
//
// The handler is typically nil, in which case the DefaultServeMux is used.
//
// ListenAndServe always returns a non-nil error.
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
上面的解释说白了,就是第二个参数,让你传入一个 handler
类型,我们再看看handler
类型
// A Handler responds to an HTTP request.
//
// ServeHTTP should write reply headers and data to the ResponseWriter
// and then return. Returning signals that the request is finished; it
// is not valid to use the ResponseWriter or read from the
// Request.Body after or concurrently with the completion of the
// ServeHTTP call.
//
// Depending on the HTTP client software, HTTP protocol version, and
// any intermediaries between the client and the Go server, it may not
// be possible to read from the Request.Body after writing to the
// ResponseWriter. Cautious handlers should read the Request.Body
// first, and then reply.
//
// Except for reading the body, handlers should not modify the
// provided Request.
//
// If ServeHTTP panics, the server (the caller of ServeHTTP) assumes
// that the effect of the panic was isolated to the active request.
// It recovers the panic, logs a stack trace to the server error log,
// and either closes the network connection or sends an HTTP/2
// RST_STREAM, depending on the HTTP protocol. To abort a handler so
// the client sees an interrupted response but the server doesn't log
// an error, panic with the value ErrAbortHandler.
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
可以看到 Handler
类型是一个自定义类型,是个接口,我们只需实现 ServeHTTP
方法即可
有关 ServeHTTP
什么时候调用,可以看看这个:
https://www.jb51.cc/html/667637.html
https://lookcos.cn/archives/1214.html
ServeHTTP
你自己实现了,它会自己调用,相当于一个中间件
- 实现一个 Handler,它负责路由
- 如果找到了路由,就执行业务代码
- 找不到就返回 404
基于 map 实现路由
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
)
// 抽象接口
/*=============================== serve层抽象开始 ==============================*/
type Server interface {
Route(method string, pattern string, handlerFunc func(ctx *Context))
Start(address string) error
}
// 结构体,相当于实现类
type sdkHttpServer struct {
// Name server 的名字
Name string
handler *HandlerBaseOnMap
}
/*=============================== 上下文层抽象开始 ==============================*/
// 上下文结构体
type Context struct {
W http.ResponseWriter
R *http.Request
}
// 读取并反序列化
// req interface{} 接受任何类型
func (c *Context) ReadJson(req interface{}) error {
// 读取body信息
r := c.R
body, err := io.ReadAll(r.Body)
if err != nil {
// 要返回掉,不然就会继续执行后面的代码
return err
}
// 将body参数json信息 解析(反序列化)到req 结构体
err = json.Unmarshal(body, req)
if err != nil {
// 要返回掉,不然就会继续执行后面的代码
return err
}
return nil
}
// 响应
func (c *Context) WriteJson(code int, resp interface{}) error {
c.W.WriteHeader(code)
// 将响应结构体输入到浏览器
respJson, err := json.Marshal(resp)
if err != nil {
return err
}
_, err = c.W.Write(respJson)
return err
}
// 200
func (c *Context) OkJson(resp interface{}) error {
return c.WriteJson(http.StatusOK, resp)
}
// 500
func (c *Context) ErrorJson(resp interface{}) error {
return c.WriteJson(http.StatusInternalServerError, resp)
}
// 对外提供创建的方法
func NewContext(writer http.ResponseWriter, request *http.Request) *Context {
return &Context{
R: request,
W: writer,
}
}
/*=============================== 上下文层抽象结束 ==============================*/
/*=============================== 路由层 map抽象开始 ==============================*/
// 路由结构体
type HandlerBaseOnMap struct {
// key 应该是 method + url
// value 应该是 与 path 对应的方法 如:signUp()
handlers map[string]func(ctx *Context)
}
// 生成key 方法
func (h *HandlerBaseOnMap) key(method string, pattern string) string {
// 将方法和路由拼接起来 最后如:GET#/index
key := method + "#" + pattern
// fmt.Printf("key => %s \n", key)
return key
}
// 初始化一个 HandlerBaseOnMap
func NewHandlers() *HandlerBaseOnMap {
return &HandlerBaseOnMap{handlers: make(map[string]func(ctx *Context))}
}
/*
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }
该方法为 http.Handle 必须实现的方法
*/
func (h *HandlerBaseOnMap) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
// 获取key
key := h.key(request.Method, request.URL.Path)
// 通过 key 找到 handlers map 中对应要执行的方法
if handler, ok := h.handlers[key]; ok {
// 找到了路由,调用对应的 handler 并自动创建 NewContext 获取 上下文结构体
handler(NewContext(writer, request))
} else {
// 返回404
writer.WriteHeader(http.StatusNotFound)
writer.Write([]byte("Not Found"))
}
}
/*=============================== 路由层 map抽象结束 ==============================*/
// 实现接口方法
// 传入method 为访问方式,pattern 为路由,handlerFunc 参数为方法,handlerFunc 就是对应传入的方法名
func (s *sdkHttpServer) Route(method string, pattern string, handlerFunc func(ctx *Context)) {
// 闭包创建
// 不够好,
/* http.HandleFunc(pattern, func(writer http.ResponseWriter,
request *http.Request) {
ctx := NewContext(writer, request)
// 调用对应的 handlerFunc 方法, 将 ctx 传入
handlerFunc(ctx)
}) */
// 基于 map 创建支持 method 方式的
// 生成key
key := s.handler.key(method, pattern)
// 插入map key 和对应 value, 也就是要执行的方法
s.handler.handlers[key] = handlerFunc
}
func (s *sdkHttpServer) Start(address string) error {
// http.Handle("/", s.handler)
// return http.ListenAndServe(address, nil)
// 启动并注册路由
return http.ListenAndServe(address, s.handler)
}
// 暴露给外部使用,返回一个 Server 结构体(其实就是实例)
func NewSdkHttpServer(name string) Server {
// 由于引用类型,所以实际是返回 指针
return &sdkHttpServer{
Name: name,
handler: NewHandlers(),
}
}
/*=============================== serve层抽象结束 ==============================*/
/*=============================== 业务部分 ==============================*/
// 登录信息结构体
type signUpReq struct {
// 导出json字符串中key名字与结构体变量命名一致,
// 我们可以通过结构体定义时指定导出后key值;并且支持如果结构体中变量为空时不导出
Email string `json:"email"`
Password string `json:"password"`
ConfirmedPassword string `json:"confirmed_password"`
}
// 返回信息结构
type commonResponse struct {
BizCode int `json:"biz_code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
// 简单注册
// func SignUp(w http.ResponseWriter, r *http.Request) {
func SignUp(ctx *Context) {
// 初始化
req := &signUpReq{}
// 传入指针
err := ctx.ReadJson(req)
if err != nil {
fmt.Fprintf(ctx.W, "err: %v", err)
// 要返回掉,不然就会继续执行后面的代码
return
}
// 反序列化之后的 req 结构体
fmt.Printf("反序列化之后的 req: %#v \n", req)
// 得到 req 请求参数结构体 ,处理业务逻辑,等等...
// 初始化返回信息结构体
resp := &commonResponse{
BizCode: 200,
Msg: "正常返回",
Data: 10086,
}
// err = ctx.WriteJson(http.StatusOK, resp)
// 不用传入状态码了
err = ctx.OkJson(resp)
if err != nil {
fmt.Fprintf(ctx.W, "写入响应失败: %v", err)
}
}
func main() {
// 自己封装
server := NewSdkHttpServer("my-server")
// 注册路由
server.Route("POST", "/SignUp", SignUp)
// 启动服务
server.Start("127.0.0.1:8080")
}
缺点
这种实现方式是有缺点的:
这样基于Map实现一个负责简单路由查找的hanfler
就基本完成了,也实现了对RESTful API
的简单支持。但从上面代码可以看出:sdkHttpServer
和HandlerBaseOnMap
强耦合;Route
方法依赖于知道 Handlers
的内部细节;当想要扩展到利用路由树来实现时,sdkHttpServer
也要修改。
使用接口实现路由
上面的缺点,我们只需要使用接口实现,就可以将上面的缺点去掉
例:
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
)
// 抽象接口
/*=============================== serve层抽象开始 ==============================*/
type Server interface {
RouteTable
Start(address string) error
}
// 结构体,相当于实现类
type sdkHttpServer struct {
// Name server 的名字
Name string
// 耦合太强
// handler *HandlerBaseOnMap
// 只依赖接口
handler MyHandler
}
// 定义自定的Handler接口
type MyHandler interface {
http.Handler
RouteTable
}
type RouteTable interface {
// route 设定一个路由,命中的会执行 handlerFunc
Route(method string, pattern string, handlerFunc func(ctx *Context))
}
/*=============================== 上下文层抽象开始 ==============================*/
// 上下文结构体
type Context struct {
W http.ResponseWriter
R *http.Request
}
// 读取并反序列化
// req interface{} 接受任何类型
func (c *Context) ReadJson(req interface{}) error {
// 读取body信息
r := c.R
body, err := io.ReadAll(r.Body)
if err != nil {
// 要返回掉,不然就会继续执行后面的代码
return err
}
// 将body参数json信息 解析(反序列化)到req 结构体
err = json.Unmarshal(body, req)
if err != nil {
// 要返回掉,不然就会继续执行后面的代码
return err
}
return nil
}
// 响应
func (c *Context) WriteJson(code int, resp interface{}) error {
c.W.WriteHeader(code)
// 将响应结构体输入到浏览器
respJson, err := json.Marshal(resp)
if err != nil {
return err
}
_, err = c.W.Write(respJson)
return err
}
// 200
func (c *Context) OkJson(resp interface{}) error {
return c.WriteJson(http.StatusOK, resp)
}
// 500
func (c *Context) ErrorJson(resp interface{}) error {
return c.WriteJson(http.StatusInternalServerError, resp)
}
// 对外提供创建的方法
func NewContext(writer http.ResponseWriter, request *http.Request) *Context {
return &Context{
R: request,
W: writer,
}
}
/*=============================== 上下文层抽象结束 ==============================*/
/*=============================== 路由层 map抽象开始 ==============================*/
// 路由结构体
type HandlerBaseOnMap struct {
// key 应该是 method + url
// value 应该是 与 path 对应的方法 如:signUp()
handlers map[string]func(ctx *Context)
}
// 生成key 方法
func (h *HandlerBaseOnMap) key(method string, pattern string) string {
// 将方法和路由拼接起来 最后如:GET#/index
key := method + "#" + pattern
// fmt.Printf("key => %s \n", key)
return key
}
// 一种常用的GO 设计模式,
// 用来确保 HandlerBaseOnMap 肯定实现了 MyHandler 接口
var _ MyHandler = &HandlerBaseOnMap{}
// 初始化一个 HandlerBaseOnMap
func NewHandlers() *HandlerBaseOnMap {
return &HandlerBaseOnMap{handlers: make(map[string]func(ctx *Context))}
}
/*
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }
该方法为 http.Handle 必须实现的方法
*/
// func (h *HandlerBaseOnMap) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
func (h *HandlerBaseOnMap) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
// 获取key
key := h.key(request.Method, request.URL.Path)
// 通过 key 找到 handlers map 中对应要执行的方法
if handler, ok := h.handlers[key]; ok {
// 找到了路由,调用对应的 handler 并自动创建 NewContext 获取 上下文结构体
handler(NewContext(writer, request))
} else {
// 返回404
writer.WriteHeader(http.StatusNotFound)
writer.Write([]byte("Not Found"))
}
}
// 实现了 RouteTable
func (h *HandlerBaseOnMap) Route(method string, pattern string, handlerFunc func(ctx *Context)) {
key := h.key(method, pattern)
h.handlers[key] = handlerFunc
}
/*=============================== 路由层 map抽象结束 ==============================*/
// 实现接口方法
// 传入method 为访问方式,pattern 为路由,handlerFunc 参数为方法,handlerFunc 就是对应传入的方法名
func (s *sdkHttpServer) Route(method string, pattern string, handlerFunc func(ctx *Context)) {
// 闭包创建
// 不够好,
/* http.HandleFunc(pattern, func(writer http.ResponseWriter,
request *http.Request) {
ctx := NewContext(writer, request)
// 调用对应的 handlerFunc 方法, 将 ctx 传入
handlerFunc(ctx)
}) */
/* // 基于 map 创建支持 method 方式的
// 生成key
key := s.handler.key(method, pattern)
// 插入map key 和对应 value, 也就是要执行的方法
s.handler.handlers[key] = handlerFunc */
// 基于接口 实际 调用的是 MyHandler , 委托的方式
s.handler.Route(method, pattern, handlerFunc)
}
func (s *sdkHttpServer) Start(address string) error {
// http.Handle("/", s.handler)
// return http.ListenAndServe(address, nil)
// 启动并注册路由
return http.ListenAndServe(address, s.handler)
}
// 暴露给外部使用,返回一个 Server 结构体(其实就是实例)
func NewSdkHttpServer(name string) Server {
// 由于引用类型,所以实际是返回 指针
return &sdkHttpServer{
Name: name,
handler: NewHandlers(),
}
}
/*=============================== serve层抽象结束 ==============================*/
/*=============================== 业务部分 ==============================*/
// 登录信息结构体
type signUpReq struct {
// 导出json字符串中key名字与结构体变量命名一致,
// 我们可以通过结构体定义时指定导出后key值;并且支持如果结构体中变量为空时不导出
Email string `json:"email"`
Password string `json:"password"`
ConfirmedPassword string `json:"confirmed_password"`
}
// 返回信息结构
type commonResponse struct {
BizCode int `json:"biz_code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
// 简单注册
// func SignUp(w http.ResponseWriter, r *http.Request) {
func SignUp(ctx *Context) {
// 初始化
req := &signUpReq{}
// 传入指针
err := ctx.ReadJson(req)
if err != nil {
fmt.Fprintf(ctx.W, "err: %v", err)
// 要返回掉,不然就会继续执行后面的代码
return
}
// 反序列化之后的 req 结构体
fmt.Printf("反序列化之后的 req: %#v \n", req)
// 得到 req 请求参数结构体 ,处理业务逻辑,等等...
// 初始化返回信息结构体
resp := &commonResponse{
BizCode: 200,
Msg: "正常返回",
Data: 10086,
}
// err = ctx.WriteJson(http.StatusOK, resp)
// 不用传入状态码了
err = ctx.OkJson(resp)
if err != nil {
fmt.Fprintf(ctx.W, "写入响应失败: %v", err)
}
}
func main() {
// 自己封装
server := NewSdkHttpServer("my-server")
// 注册路由
server.Route("POST", "/SignUp", SignUp)
// 启动服务
server.Start("127.0.0.1:8080")
}
支持filter(中间件)
一般框架都支持,过滤器或者叫中间件,这也是AOP,面向切面,这里为 server 支持一些 AOP逻辑
- AOP:横向关注点,一般用于解决 Log, tracing,metric,熔断,限流等
- filter:我们希望请求在真正被处理之前能够经过一大堆的 filter
如
filter定义和模拟实现
package main
import (
"fmt"
"time"
)
type Filter func(ctx *Context)
type FilerBuilder func(next Filter) Filter
var _ FilerBuilder = MetricsFilterBuilder
func MetricsFilterBuilder(next Filter) Filter {
return func(ctx *Context) {
start := time.Now().Nanosecond()
next(ctx)
end := time.Now().Nanosecond()
fmt.Printf("用了 %d 纳秒", end-start)
}
}
为什么这么定义
- 考虑我们 metric filter
- 在请求执行前,我记录时间戳
- 在请求执行后,我记录时间戳
- 两者相减就是执行时间
- 同时保证线程安全
- 是两个filter 一前一后,那么要考虑线程安全问题
- 要考虑开始时间怎么传递给后一个filter
- 如果是一个filter,不采用这种方式,那么怎么把filter串起来?
github
这里不一一实现了,这里给出
github:https://github.com/makalochen/toy-web/tree/main/pkg
可以自行查看
当然,我也是跟着别人来的,我只是一步步理解稍作修改
标签:http,string,err,架子,json,func,go,return From: https://www.cnblogs.com/makalochen/p/17088308.html