当前位置: 首页 > 工具软件 > gonet2 > 使用案例 >

GO标准包之net/http详细介绍

徐柏
2023-12-01

理解 HTTP 构建的网络应用只要关注两个端—客户端(clinet)和服务端(server),两个端的交互来自 clinet 的 request,以及server端的response。所谓的http服务器,主要在于如何接受 clinet 的 request,并向client返回response。

接收request的过程中,最重要的莫过于路由(router),即实现一个Multiplexer器。Go中既可以使用内置的mutilplexer — DefautServeMux,也可以自定义。Multiplexer路由的目的就是为了找到处理器函数(handler),后者将对request进行处理,同时构建response。
Clinet -> Requests -> [Multiplexer(router) -> handler -> Response -> Clinet

因此,理解go中的http服务,最重要就是要理解Multiplexer和handler,Golang中的Multiplexer基于ServeMux结构,同时也实现了Handler接口。

对于handler的其实没有合适的中文词语,只可意会,不可言传的感觉。为了更好的说明问题,本文约定了如下规则:

hander函数: 具有func(w http.ResponseWriter, r *http.Requests)签名的函数

handler处理器(函数): 经过HandlerFunc结构包装的handler函数,它实现了ServeHTTP接口方法的函数。调用handler处理器的ServeHTTP方法时,即调用handler函数本身。

handler对象:实现了Handler接口ServeHTTP方法的结构。
handler处理器和handler对象的差别在于,一个是函数,另外一个是结构,它们都有实现了ServeHTTP方法。很多情况下它们的功能类似,下文就使用统称为handler。这算是Golang通过接口实现的类动态类型吧。

Handler
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
从handler的结构来看,需要实现servehttp的方法

servemux
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry
hosts bool
}

type muxEntry struct {
explicit bool
h Handler
pattern string
}

从这个里面可以看出,mux最重要的部分是muxentry的map结构
其中key代表的是url的一些模式,value是muxentry结构

当然,所谓的ServeMux也实现了ServeHTTP接口,也算是一个handler,不过ServeMux的ServeHTTP方法不是用来处理request和respone,而是用来找到路由注册的handler,后面再做解释。

server
除了ServeMux和Handler,还有一个结构Server需要了解。从http.ListenAndServe的源码可以看出,它创建了一个server对象,并调用server对象的ListenAndServe方法:

func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}

而server的结构如下
type Server struct {
Addr string
Handler Handler
ReadTimeout time.Duration
WriteTimeout time.Duration
TLSConfig *tls.Config

MaxHeaderBytes int

TLSNextProto map[string]func(*Server, *tls.Conn, Handler)

ConnState func(net.Conn, ConnState)
ErrorLog *log.Logger
disableKeepAlives int32     nextProtoOnce     sync.Once 
nextProtoErr      error     

}
可以看到server存储了服务器处理请求常见的字段,其中handler字段保留了handler接口。如果server没有handler对象,就会使用defautmux来做mux。

创建HTTP服务
创建http服务首先需要注册路由(提供url以及handler的映射)
然后实例化一个对象,开启对客户端的监听
对于net/http的开启http服务,可以有两种写法
注册路由:
Http.handlerfunc("",indexhandler)
开启监听:
Http.listenandserver(addr,handler)

注册路由
Http.handlerfunc选取了defautservemux作为mux

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}

什么是defautservemux?
defaultservemux是servemux的一个实例。也提供了使用new的方法创建一个servemux。

// NewServeMux allocates and returns a new ServeMux.
func NewServeMux() *ServeMux { return new(ServeMux) }

// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux

var defaultServeMux ServeMux

Golang标准库http包提供了基础的http服务,这个服务又基于Handler接口和ServeMux结构的做Mutilpexer。实际上,go的作者设计Handler这样的接口,不仅提供了默认的ServeMux对象,开发者也可以自定义ServeMux对象。

本质上ServeMux只是一个路由管理器,而它本身也实现了Handler接口的ServeHTTP方法。因此围绕Handler接口的方法ServeHTTP,可以轻松的写出go中的中间件。

自定义HANDLER
就是用户可以自己定义一个handler,只要能够实现servehttp的方法就行

type textHandler struct {
responseText string
}

func (th *textHandler) ServeHTTP(w http.ResponseWriter, r *http.Request){
fmt.Fprintf(w, th.responseText)
}

type indexHandler struct {}

func (ih *indexHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Header().Set(“Content-Type”, “text/html”)

html := `<doctype html>
    <html>
    <head>
      <title>Hello World</title>
    </head>
    <body>
    <p>
      <a href="/welcome">Welcome</a> |  <a href="/message">Message</a>
    </p>
    </body>
` fmt.Fprintln(w, html) }

func main() {
mux := http.NewServeMux()

mux.Handle("/", &indexHandler{})

thWelcome := &textHandler{"TextHandler !"}
mux.Handle("/text",thWelcome)

http.ListenAndServe(":8000", mux)

}
上面自定义了两个handler结构,都实现了ServeHTTP方法。我们知道,NewServeMux可以创建一个ServeMux实例,ServeMux同时也实现了ServeHTTP方法,因此代码中的mux也是一种handler。把它当成参数传给http.ListenAndServe方法,后者会把mux传给Server实例。因为指定了handler,因此整个http服务就不再是DefaultServeMux,而是mux,无论是在注册路由还是提供请求服务的时候。

有一点值得注意,这里并没有使用HandleFunc注册路由,而是直接使用了mux注册路由。当没有指定mux的时候,系统需要创建一个默认的defaultServeMux,此时我们已经有了mux,因此不再需要http.HandleFucn方法了,直接使用mux的Handle方法注册即可。

此外,Handle第二个参数是一个handler(处理器),并不是HandleFunc的一个handler函数,其原因也是因为mux.Handle本质上就需要绑定url的pattern模式和handler(处理器)即可。既然indexHandler是handle(处理器),当然就能作为参数,一切请求的处理过程,都交给器实现的接口方法ServeHTTP就行了。

创建handler处理器
直接创立handler函数,然后调用go的方法把函数转换成handler
func text(w http.ResponseWriter, r *http.Request){
fmt.Fprintln(w, “hello world”)
}

func index(w http.ResponseWriter, r *http.Request) {
w.Header().Set(“Content-Type”, “text/html”)

html := `<doctype html>
    <html>
    <head>
      <title>Hello World</title>
    </head>
    <body>
    <p>
      <a href="/text">Welcome</a> |  <a href="/message">Message</a>
    </p>
    </body>
` fmt.Fprintln(w, html) }

func main() {
mux := http.NewServeMux()
mux.Handle("/", http.HandlerFunc(index))
mux.HandleFunc("/text", text)
http.ListenAndServe(“localhost:8081”, mux)
}

通过这个就可以实现一个http服务

自定义server
如果说自定义的server没有提供mux,那么将会使用defaultservemux
如下图
func main(){
http.HandleFunc("/", index)

server := &http.Server{
    Addr: ":8000",
    ReadTimeout: 60 * time.Second,
    WriteTimeout: 60 * time.Second,
}
server.ListenAndServe()

}
创建server的时候需要给出addr,readtime,writetime
然后进行监听活动

如果自定义了mux
func main() {

mux := http.NewServeMux()
mux.HandleFunc("/", index)

server := &http.Server{
    Addr: ":8000",
    ReadTimeout: 60 * time.Second,
    WriteTimeout: 60 * time.Second,
    Handler: mux,
}
server.ListenAndServe()

}

对于HTTP,是一个以请求和响应为应答模式的传输协议

请求的结构
type Request struct {
Method string

URL *url.URL
Proto      string // "HTTP/1.0"
ProtoMajor int    // 1
ProtoMinor int    // 0
Header Header
Body io.ReadCloser
ContentLength int64
TransferEncoding []string
Close bool
Host string
Form url.Values
PostForm url.Values
MultipartForm *multipart.Form


ctx context.Context
}

URL
go里面提供了一个URL结构来映射到HTTP的URL

type URL struct {
Scheme string
Opaque string
User *Userinfo
Host string
Path string
RawQuery string
Fragment string
}

URL的格式比较明确,更好的名词应该是URI—>统一资源定位
URL中比较重要的是查询字符串query,通常作为get请求的参数
query是一些用&符号分割的键值对
但是url的编码是ASSIC吗,需要query进行urlencode。
go中可以通过request.URI.Rawquery来读取query

func indexHandler(w http.ResponseWriter, r *http.Request) {
info := fmt.Sprintln(“URL”, r.URL, “HOST”, r.Host, “Method”, r.Method, “RequestURL”, r.RequestURI, “RawQuery”, r.URL.RawQuery)
fmt.Fprintln(w, info)
}
通过这个就可以读出request的信息

hander
hander是http的重要组成部分,request中有hander结构,hander本身是一个map结构,将http协议的header的key-value映射成一个图

Host: example.com
accept-encoding: gzip, deflate
Accept-Language: en-us
fOO: Bar
foo: two


Header = map[string][]string{
    "Accept-Encoding": {"gzip, deflate"},
    "Accept-Language": {"en-us"},
    "Foo": {"Bar", "two"},
}

Golng 提供了不少打印函数,基本上分为三类三种。即 Print Println 和Printf。
Print比较简单,打印输出到标准输出流,Println则也一样不同在于多打印一个换行符。至于Printf则是打印格式化字符串,三个方法都返回打印的bytes数。Sprint,Sprinln和Sprintf则返回打印的字符串,不会输出到标准流中。Fprint,Fprintf和Fprinln则把输出的结果打印输出到io.Writer接口中,http中则是http.ReponseWriter这个对象中,返回打印的bytes数。

BODY
http的数据通信主要通过body进行传输
go把body封装成request的body,这是一个readcloser接口
接口方法reader也是一个接口,后者具有一个read(p []byte)的方法,于是body可以通过读取byte数组来获得请求的数据

func indexHandler(w http.ResponseWriter, r *http.Request) {

info := fmt.Sprintln(r.Header.Get("Content-Type"))
len := r.ContentLength
body := make([]byte, len)
r.Body.Read(body)
fmt.Fprintln(w, info, string(body))

}

response
请求和相应的报文是类似的,相关的处理和结构也是相似的。go构造响应的结构是responsewriter接口

type ResponseWriter interface {
Header() Header
Write([]byte) (int, error)
WriteHeader(int)
}

可以看到同样具有一个header方法返回一个hander的map结构
writeheader会返回响应的状态码
write返回的是客户端的数据

func indexHandler(w http.ResponseWriter, r *http.Request) {
str := `

Go Web Programming

Hello World

` w.Write([]byte(str)) } 通过这种方式就可以写入返回给客户端的数据

同样可以写入状态码,,,,注意 不能往head的数据里面写东西,不然还会显示write的东西
w.writerheader(404)

重定向
通过head.set重新定位

w.Header().Set(“Location”, “https://google.com”)
w.WriteHeader(302)

与请求的Header结构一样,w.Header也有几个方法用来设置headers

func (h Header) Add(key, value string) {
textproto.MIMEHeader(h).Add(key, value)
}

func (h Header) Set(key, value string) {
textproto.MIMEHeader(h).Set(key, value)
}

func (h MIMEHeader) Add(key, value string) {
key = CanonicalMIMEHeaderKey(key)
h[key] = append(h[key], value)
}

func (h MIMEHeader) Set(key, value string) {
h[CanonicalMIMEHeaderKey(key)] = []string{value}
}

Set和Add方法都可以设置headers,对于已经存在的key,Add会追加一个值value的数组中,,set则是直接替换value的值。即 append和赋值的差别。

 类似资料: