侧边栏壁纸
博主头像
hanlibaby博主等级

念念不忘,必有回响

  • 累计撰写 59 篇文章
  • 累计创建 92 个标签
  • 累计收到 20 条评论

Golang Web(一) 基础

hanlibaby
2022-04-19 / 0 评论 / 0 点赞 / 49 阅读 / 4,632 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2022-04-26,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

net/http 标准库

net/http 包是 Go 内置的一个十分优秀的包,它给我们提供了 HTTP网络编程 的各种接口,便于我们实现客户端和服务端。

http server

Go 中启动的一个HTTP服务端很方便,只需要下面一行代码即可。

http.ListenAndServe(addr string, handler Handler)

第一个参数 addr 表示监听的端口号,第二个参数用于处理所有请求的处理器,这个参数一般都是为 nil

简单示例

package main

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

func indexHandler(w http.ResponseWriter, r *http.Request) {
	_, err := fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path)
	if err != nil {
		log.Println("print error")
	}
}

func helloHandler(w http.ResponseWriter, r *http.Request) {
	_, err := io.WriteString(w, "Hello World\n")
	if err != nil {
		log.Println("write string error")
	}
}

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

这里设置了两个路由,"/""/hello" 。这两个路由分别绑定 indexHandlerhelloHandler 处理器。

根据不同的 HTTP 请求,会调用不同的处理器。访问 "/" 会响应 URL.Path = / ,而访问 "/hello" 则会响应 Hello World

可以打开浏览器测试一下,分别访问这两个路由会看到如下结果。

  1. "/"

image.png

  1. "/hello"

image.png

自定义 http.Handler 接口

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

func ListenAndServe(addr string, handler Handler)

在上面讲到 ListenAndServe 方法的第二个参数是一个用于处理所有请求的处理器,通过查看源码可以发现 Hanlder 实际是一个接口。需要实现 ServeHTTP 方法,那么也就是说,我们可以自定义一个 实现了 ServeHTTP 方法的 Hanlder 实例,让请求交给这个实例处理。

http_base.go

package main

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

// ServeHandler A handler that accepts all requests
type ServeHandler struct{}

func (serveHandler *ServeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	switch r.URL.Path {
	case "/":
		_, err := fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path)
		if err != nil {
			return
		}
	case "/hello":
		_, err := io.WriteString(w, "Hello World\n")
		if err != nil {
			return
		}
	}
}

func main() {
	serveHandler := &ServeHandler{}
	log.Fatal(http.ListenAndServe(":8080", serveHandler))
}
  • 这边定义了一个空的结构体 ServeHandler,实现了 Handler 接口的 ServeHTTP 方法,这个方法有2个参数,第二个参数是 Request ,该对象包含了该 HTTP 请求的所有的信息,比如请求地址、HeaderBody 等信息;第一个参数是 ResponseWriter ,利用 ResponseWriter 可以构造针对该请求的响应。
  • 在调用 ListenAndServe 方法的时候,我们将新建的 ServeHandler 实例传入,将所有的 HTTP 请求转向了我们自己的处理逻辑。在此之前,我们调用 http.HandleFunc 实现了路由和 Handler 的映射,也就是只能针对具体的路由写处理逻辑。比如 /hello 。但是在实现 ServeHandler之后,我们拦截了所有的 HTTP 请求,拥有了统一的控制入口。在这里我们可以自由定义路由映射的规则,也可以统一添加一些处理逻辑,例如日志、异常处理等。

更进一步

既然都已经实现了自己的 ServeHandler,那么我们可以更进一步将 ServeHTTP 方法中的 case 用一个哈希表(暂时)替代,新增一个可以添加路由规则的方法,这样路由处理就不会写死了。

代码结构

image.png

1、go.work

go 1.18

use (
	./guru
	./test
)

replace gulu => ./guru

1、gulu.go

package guru

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

// HandleFunc request handler function
type HandleFunc func(w http.ResponseWriter, r *http.Request)

// ServeHandler A handler that accepts all requests
type ServeHandler struct {
	router map[string]HandleFunc
}

// New Constructor of guru.ServeHandler
func New() *ServeHandler {
	return &ServeHandler{router: make(map[string]HandleFunc)}
}

func (serveHandler *ServeHandler) addRoute(methodKey, pattern string, handleFunc HandleFunc) {
	routeKey := methodKey + ":" + pattern
	serveHandler.router[routeKey] = handleFunc
}

// GET define a get request
func (serveHandler *ServeHandler) GET(pattern string, handleFunc HandleFunc) {
	serveHandler.addRoute("GET", pattern, handleFunc)
}

// POST define a post request
func (serveHandler *ServeHandler) POST(pattern string, handleFunc HandleFunc) {
	serveHandler.addRoute("POST", pattern, handleFunc)
}

func (serveHandler *ServeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	routeKey := r.Method + ":" + r.URL.Path
	if handleFunc, hasHandler := serveHandler.router[routeKey]; hasHandler {
		handleFunc(w, r)
	} else {
		w.WriteHeader(http.StatusNotFound)
		_, err := fmt.Fprintf(w, "404 NOT FOUND: %s\n", r.URL)
		if err != nil {
			return
		}
	}
}

// Run define a method to start http server
func (serveHandler *ServeHandler) Run(addr string) {
	err := http.ListenAndServe(addr, serveHandler)
	if err != nil {
		log.Println("server start error")
	}
}
  1. 首先定义了 `HandlerFunc· 类型,这是提供给使用者的,用来定义路由映射的处理方法。
  2. ServeHandler 中,加入了一张路由映射表 Routerkey 由请求方法类型和请求路由地址构成,例如:GET:/GET:/helloPOST:/hello 等,这样的话,如果针对相同的路由,那么就可以通过请求方法类型来区分,映射到不同的 Handlervalue 是使用者定义的处理逻辑。
  3. 当调用 (*ServeHandler).GET() 或者 (*ServeHandler).POST() 方法时,会将路由和处理方法添加到路由表 Router 中。
  4. 这里 ServeHandler 实现的 ServeHTTP 方法的作用就是解析请求的路由,然后在路由表中查找,如果找到了则执行相应的处理方法,否则就返回 404 NOT FOUND 响应。

2、main.go

package main

import (
	"fmt"
	"io"
	"lwjppz.cn/guru"
	"net/http"
)

func main() {
	server := guru.New()

	server.GET("/", func(w http.ResponseWriter, r *http.Request) {
		_, err := fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path)
		if err != nil {
			return
		}
	})

	server.GET("/hello", func(w http.ResponseWriter, r *http.Request) {
		_, err := io.WriteString(w, "Hello World\n")
		if err != nil {
			return
		}
	})

	server.Run(":8080")
}

最终实现的效果和之前的一样。

总结

其实到这里,一个简单的Web框架原型就出来了。实现了路由映射表,提供了注册、处理路由的方法,并且包装了启动服务的函数。不过现在还没有实现比 net/http 标准库更强大的功能,当然,后续我们可以将动态路由,中间件等功能添加进去。

0

评论区