最近看到一些不错的博客系列文章,包括为@极客兔兔原创的7天从零系列的文章,此系列文章为个人阅读及个人描述记录。

其中序言部分主要描述的是一个web框架大概需要实现的内容,此处借鉴了Gin的设计思想。

启动Web服务

使用 net/http 库,封装了http的网络编程的基础的接口。
d1-http-v1/main.go

v1版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

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

func main() {
// 映射路由
http.HandleFunc("/", indexHandler)
http.HandleFunc("/hello", helloHandler)
// 启动 web 服务
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)
}
}

这里直接通过 http.HandleFunc 函数将具体处理函数与路由做好映射关系。

关键函数分析

  • http.ListenAndServe:
    通过传入 addrhandler, addrstring 类型,重点再看下 Handler 的定义
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 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 接口中定义的 ServeHTTP(ResponseWriter, *Request) 即实现了对 http 请求的处理。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    // 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)
    }
    以此引申 v2 版本

v2 版本

项目结构如下:
项目结构

在项目中引用了本地包的依赖,这里需要对项目中内部模块进行依赖配置:
在根目录中的 go.mod 文件进行如下配置

1
2
3
4
5
6
7
8
9
10
11
module kgeedemo

go 1.15

require (
kgee v0.0.0
)

replace (
kgee => ./kgee
)
  • main.go

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    package main

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

    // 初始化-> 路由映射缓存 -> 启动服务
    func main() {
    // 初始化 kgee,类似于门面模式,将需要处理的工作在函数内部处理了。
    r := kgee.New()
    // args[1] 参数为 函数
    r.GET("/", func(w http.ResponseWriter, req *http.Request) {
    _, _ = fmt.Fprintf(w, "URL.Path = %q\n", req.URL.Path)
    })
    r.GET("/hello", func(w http.ResponseWriter, req *http.Request) {
    for k, v := range req.Header {
    _, _ = fmt.Fprintf(w, "Header[%q] = %q\n", k, v)
    }
    })
    // 服务器运行
    _ = r.Run(":9999")
    }

  • kgee.go

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    package kgee

    import (
    "fmt"
    "net/http"
    )

    //HandlerFunc defines the reques handler used by kgee
    type HandlerFunc func(w http.ResponseWriter, req *http.Request)

    //Engin implement the interface of ServeHTTP
    type Engine struct {
    router map[string]HandlerFunc
    }

    //New is the constructor of kgee.Engine
    func New() *Engine {
    return &Engine{router: make(map[string]HandlerFunc)}
    }

    func (engine *Engine) addRoute(method string, pattern string, handler HandlerFunc) {
    key := method + "-" + pattern
    engine.router[key] = handler
    }

    func (engin *Engine) GET(pattern string, handler HandlerFunc) {
    engin.addRoute("GET", pattern, handler)
    }

    func (engine *Engine) POST(pattern string, hander HandlerFunc) {
    engine.addRoute("POST", pattern, hander)
    }

    // 完成 ServeHttp 函数
    func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    key := req.Method + "-" + req.URL.Path
    if handler, ok := engine.router[key]; ok {
    handler(w, req)
    } else {
    fmt.Println(w, "404 NOT FOUND:%s\n", req.URL)
    }
    }

    func (engine *Engine) Run(addr string) (err error) {
    return http.ListenAndServe(addr, engine)
    }

    至此,一个简单示例即完成,如下我们可以验证下。

验证

这里我 Goland 中集成了 RestConsole 插件,直接用这个插件进行测试。测试结果如下:

  • 请求 / 对应的路由结果如下:
    r1
  • 请求 /hello 对应的路由结果如下:
    r2

参考链接:

https://geektutu.com/post/gee-day1.html

本文代码:

https://github.com/kiragoo/kgee/tree/day1