栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Go语言

Go语言圣经 - 第7章 接口 - 7.7 http.Handler 接口

Go语言 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Go语言圣经 - 第7章 接口 - 7.7 http.Handler 接口

第7章 接口

接口类型是对其它类型行为的抽象和概括.接口类型不会和特定的实现细节绑定在一起,这种抽象的方式能让我们的函数更加的灵活和更具有适应能力

Go语言的接口比较特殊,因为它是满足隐式实现的。也就是说,我们无需给具体类型定义所有满足足的接口类型,只需要让类型拥有一些简单必要的方法。这样我们新建一个接口类型,满足具体类型,并且我们不需要更改这些类型的定义。当我们使用的类型来自于不受我们控制的包时,这种机制比较有用

7.7 http.Handler 接口

第一章,我们粗略了解了用net/http包实现网络客户端和服务器,本节我们将会对那些基于http.Hander接口的服务器API做更进一步的学习

package http

type Handler interface {
	ServeHTTP(w ResponseWtiter,r *Request)
}

func ListenAndServe(address string, h Handler) error

ListenAndServe 函数需要一个l例如“localhost:8000”的地址和一个Handler接口实例

我们现在来构建一个电子商务网站,当我们访问特定服务器地址时,我们就可以看到产品和其价格信息,在此之前我们先得把这些基本的信息在底层构建好

type dollars float32 

func(d dollars) String() string {return fmt.Sprintf("$%.2f",d)}

type database map[string]dollars

func (db database) ServeHTTP(w http.ResponseWriter,req *http.Request) {
	for item, price := range db {
		fmt.Fprintf(w,"%s: %sn",item,price)
	}
}

func main() {
	db := database{"shoes":50,"socks":5}

	log.Fatal(http.ListenAndServe("localhost:8000",db))
}

现在运行程序,当我们访问浏览器时,便可以看到如下信息

shoes: $50.00
socks: $5.00

虽然我们现在可以访问这个地址并且能够成功获得信息,但是我们的信息不够细致,在实践中,我们更多的业务逻辑是访问特定的路径获得特定的信息

现在我们来实现这一点:我们使用了一个switch结构

func main() {
	db := database{"shoes":50,"socks":5}
	log.Fatal(http.ListenAndServe("localhost:8000",db))
}

type dollars float32

func(d dollars) String() string {return fmt.Sprintf("$%.2f",d)}

type database map[string]dollars

func (db database) ServeHTTP(w http.ResponseWriter,req *http.Request) {
	switch req.URL.Path {
	case "/list":
		for item,price :=range db {
			fmt.Fprintf(w,"%s:%sn",item,price)
		}
	case "/price":
		item:= req.URL.Query().Get("item")
		price,ok := db[item]
		if !ok {
			w.WriteHeader(http.StatusNotFound)
			fmt.Fprintf(w,"no such itme:%qn",item)

			return
		}
		fmt.Fprintf(w,"%sn",price)
	default:
		w.WriteHeader(http.StatusNotFound)
		fmt.Fprintf(w,"no such page:%sn",req.URL)
	}
}

运行并且访问如下地址

http://localhost:8000/price?item=socks

$5.00

http://localhost:8000/price?item=hat

no such itme:"hat"

显然,我们可以通过增加case的方式来实现不同情况下的分流,但是在实际中,我们一般把不同的case写成不同的函数或者方法

另外,路径相似的url处理的逻辑也是相似的,因此,net/http包提供了一个请求多路器ServerMux来简化和handlers的联系

一个ServeMux将一批http.Handlers聚集到一个单一的http.Handler中,这也再一次表明满足同一个接口的不同类型是可以替换的:web服务器将请求指派给任意的http.Handler而不需要考虑它后面的具体类型

对于复杂的应用,一些ServeMux可以通过组合来处理更加复杂的路由请求,Go语言没有目前没有一个权威的Web框架,就像Ruby语言有Rails和python有Django,这并不是Go语言的劣势,而是Go语言太强用不上

在下面的程序中,我们创建一个ServeMux并且使用它将URL和相应处理/list和Price操作的handle联系起来,这些操作的逻辑都已经分配到不同的方法中了,然后我们再调用ListenAndServe函数中使用ServeMux为主要的handler

func main() {
    db := database{"shoes": 50, "socks": 5}
    mux := http.NewServeMux()
    mux.Handle("/list", http.HandlerFunc(db.list))
    mux.Handle("/price", http.HandlerFunc(db.price))
    log.Fatal(http.ListenAndServe("localhost:8000", mux))
}

type database map[string]dollars

func (db database) list(w http.ResponseWriter, req *http.Request) {
    for item, price := range db {
        fmt.Fprintf(w, "%s: %sn", item, price)
    }
}

func (db database) price(w http.ResponseWriter, req *http.Request) {
    item := req.URL.Query().Get("item")
    price, ok := db[item]
    if !ok {
        w.WriteHeader(http.StatusNotFound) // 404
        fmt.Fprintf(w, "no such item: %qn", item)
        return
    }
    fmt.Fprintf(w, "%sn", price)
}

我们看看两个注册到handlers的调用,第一个db,list是一个方法值,它是下面这个类型的值

func (w http.ResponseWriter,rep *http.Request)

也就是说db.list的调用会援引一个接收者是db的database.list方法.所以db.list是一个实现了handler类似行为的函数,但是它因为没有方法,所以它不满足http.Handler接口,并且不能直接传递给mux.Handle

语句http.HandlerFunc(db.list)是一个转换而非函数调用,因为http.HandlerFunc是一个类型,定义如下:

package http

type HandlerFunc func(w ResponseWriter,r *Request)

func (f HandlerFunc) ServeHTTP(w ResponseWriter,r *Request) {
	f(w,r)
}

HanflerFunc显示了在Go语言中接口机制中比较特殊的一点,这是一个实现了http.Handler的函数类型。ServeHTTP方法的行为是调用了它函数的本身,因此HandlerFunc是一个让函数满足一个接口的适配器,这里函数和这个接口仅有的方法有着相同的签名

实际上,这个技巧让一个单一的类型例如database以多种方式满足http.Handler接口:一种通过它的list方法,一种通过它的price方法等

因为handler通过这种方式注册非常普遍,ServeMux有一个非常方便的HandlerFunc方法,它帮助我们简化handler注册代码成这样

mux.HandlerFunc("/list",db.list)
mux.HandlerFunc("/price",db.list)

从上面的代码中很容易看出应该怎么构建一个程序:两个不同的web服务器监听不同的端口,并且定义不同的URL将它们指派到不同handler,我们只需要再构建一个ServeMux并且再调用一次ListenAndServe

但是在大多数程序中,一个web服务器就足够了,在一个应用程序的多个文件中定HTTP handler也非常典型,如果它们必须全部都显式的注册到这个应用的ServeMux实例上会比较麻烦

所以为了方便,net/http包提供了一个全局的ServeMux实例DefaultAndServeMux和包级别的http.handle和http.HandlerFunc函数。现在为了使用DefaultServeMux作为服务器的主handler,我们不需要将它传递给ListenAndServe函数;nil值就可以工作

然后服务器就简化成:

func main() {
    db := database{"shoes": 50, "socks": 5}
    http.HandleFunc("/list", db.list)
    http.HandleFunc("/price", db.price)
    log.Fatal(http.ListenAndServe("localhost:8000", nil))
}

注意:web服务器在一个新的协程种调用每一个handler,所以当handler获取其它协程或者这个handler本身的其它请求可以访问到变量时,一定要使用预防措施,比如锁机制。后面我们将会讲到并发相关的知识

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/945438.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号