mirror of
https://github.com/gopl-zh/gopl-zh.github.com.git
synced 2025-11-01 10:41:35 +00:00
回到简体
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
## 7.7. http.Handler接口
|
||||
|
||||
在第一章中,我們粗略的了解了怎麽用net/http包去實現網絡客戶端(§1.5)和服務器(§1.7)。在這個小節中,我們會對那些基於http.Handler接口的服務器API做更進一步的學習:
|
||||
在第一章中,我们粗略的了解了怎么用net/http包去实现网络客户端(§1.5)和服务器(§1.7)。在这个小节中,我们会对那些基于http.Handler接口的服务器API做更进一步的学习:
|
||||
|
||||
<u><i>net/http</i></u>
|
||||
```go
|
||||
@@ -13,9 +13,9 @@ type Handler interface {
|
||||
func ListenAndServe(address string, h Handler) error
|
||||
```
|
||||
|
||||
ListenAndServe函數需要一個例如“localhost:8000”的服務器地址,和一個所有請求都可以分派的Handler接口實例。它會一直運行,直到這個服務因爲一個錯誤而失敗(或者啟動失敗),它的返迴值一定是一個非空的錯誤。
|
||||
ListenAndServe函数需要一个例如“localhost:8000”的服务器地址,和一个所有请求都可以分派的Handler接口实例。它会一直运行,直到这个服务因为一个错误而失败(或者启动失败),它的返回值一定是一个非空的错误。
|
||||
|
||||
想象一個電子商務網站,爲了銷售它的數據庫將它物品的價格映射成美元。下面這個程序可能是能想到的最簡單的實現了。它將庫存清單模型化爲一個命名爲database的map類型,我們給這個類型一個ServeHttp方法,這樣它可以滿足http.Handler接口。這個handler會遍歷整個map併輸出物品信息。
|
||||
想象一个电子商务网站,为了销售它的数据库将它物品的价格映射成美元。下面这个程序可能是能想到的最简单的实现了。它将库存清单模型化为一个命名为database的map类型,我们给这个类型一个ServeHttp方法,这样它可以满足http.Handler接口。这个handler会遍历整个map并输出物品信息。
|
||||
|
||||
<u><i>gopl.io/ch7/http1</i></u>
|
||||
```go
|
||||
@@ -37,14 +37,14 @@ func (db database) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
```
|
||||
|
||||
如果我們啟動這個服務,
|
||||
如果我们启动这个服务,
|
||||
|
||||
```
|
||||
$ go build gopl.io/ch7/http1
|
||||
$ ./http1 &
|
||||
```
|
||||
|
||||
然後用1.5節中的獲取程序(如果你更喜歡可以使用web瀏覽器)來連接服務器,我們得到下面的輸出:
|
||||
然后用1.5节中的获取程序(如果你更喜欢可以使用web浏览器)来连接服务器,我们得到下面的输出:
|
||||
|
||||
```
|
||||
$ go build gopl.io/ch1/fetch
|
||||
@@ -53,7 +53,7 @@ shoes: $50.00
|
||||
socks: $5.00
|
||||
```
|
||||
|
||||
目前爲止,這個服務器不考慮URL隻能爲每個請求列出它全部的庫存清單。更眞實的服務器會定義多個不同的URL,每一個都會觸發一個不同的行爲。讓我們使用/list來調用已經存在的這個行爲併且增加另一個/price調用表明單個貨品的價格,像這樣/price?item=socks來指定一個請求參數。
|
||||
目前为止,这个服务器不考虑URL只能为每个请求列出它全部的库存清单。更真实的服务器会定义多个不同的URL,每一个都会触发一个不同的行为。让我们使用/list来调用已经存在的这个行为并且增加另一个/price调用表明单个货品的价格,像这样/price?item=socks来指定一个请求参数。
|
||||
|
||||
<u><i>gopl.io/ch7/http2</i></u>
|
||||
```go
|
||||
@@ -79,16 +79,16 @@ func (db database) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
```
|
||||
|
||||
現在handler基於URL的路徑部分(req.URL.Path)來決定執行什麽邏輯。如果這個handler不能識别這個路徑,它會通過調用w.WriteHeader(http.StatusNotFound)返迴客戶端一個HTTP錯誤;這個檢査應該在向w寫入任何值前完成。(順便提一下,http.ResponseWriter是另一個接口。它在io.Writer上增加了發送HTTP相應頭的方法。)等效地,我們可以使用實用的http.Error函數:
|
||||
现在handler基于URL的路径部分(req.URL.Path)来决定执行什么逻辑。如果这个handler不能识别这个路径,它会通过调用w.WriteHeader(http.StatusNotFound)返回客户端一个HTTP错误;这个检查应该在向w写入任何值前完成。(顺便提一下,http.ResponseWriter是另一个接口。它在io.Writer上增加了发送HTTP相应头的方法。)等效地,我们可以使用实用的http.Error函数:
|
||||
|
||||
```go
|
||||
msg := fmt.Sprintf("no such page: %s\n", req.URL)
|
||||
http.Error(w, msg, http.StatusNotFound) // 404
|
||||
```
|
||||
|
||||
/price的case會調用URL的Query方法來將HTTP請求參數解析爲一個map,或者更準確地説一個net/url包中url.Values(§6.2.1)類型的多重映射。然後找到第一個item參數併査找它的價格。如果這個貨品沒有找到會返迴一個錯誤。
|
||||
/price的case会调用URL的Query方法来将HTTP请求参数解析为一个map,或者更准确地说一个net/url包中url.Values(§6.2.1)类型的多重映射。然后找到第一个item参数并查找它的价格。如果这个货品没有找到会返回一个错误。
|
||||
|
||||
這里是一個和新服務器會話的例子:
|
||||
这里是一个和新服务器会话的例子:
|
||||
|
||||
```
|
||||
$ go build gopl.io/ch7/http2
|
||||
@@ -107,12 +107,12 @@ $ ./fetch http://localhost:8000/help
|
||||
no such page: /help
|
||||
```
|
||||
|
||||
顯然我們可以繼續向ServeHTTP方法中添加case,但在一個實際的應用中,將每個case中的邏輯定義到一個分開的方法或函數中會很實用。此外,相近的URL可能需要相似的邏輯;例如幾個圖片文件可能有形如/images/\*.png的URL。因爲這些原因,net/http包提供了一個請求多路器ServeMux來簡化URL和handlers的聯繫。一個ServeMux將一批http.Handler聚集到一個單一的http.Handler中。再一次,我們可以看到滿足同一接口的不同類型是可替換的:web服務器將請求指派給任意的http.Handler
|
||||
而不需要考慮它後面的具體類型。
|
||||
显然我们可以继续向ServeHTTP方法中添加case,但在一个实际的应用中,将每个case中的逻辑定义到一个分开的方法或函数中会很实用。此外,相近的URL可能需要相似的逻辑;例如几个图片文件可能有形如/images/\*.png的URL。因为这些原因,net/http包提供了一个请求多路器ServeMux来简化URL和handlers的联系。一个ServeMux将一批http.Handler聚集到一个单一的http.Handler中。再一次,我们可以看到满足同一接口的不同类型是可替换的:web服务器将请求指派给任意的http.Handler
|
||||
而不需要考虑它后面的具体类型。
|
||||
|
||||
對於更複雜的應用,一些ServeMux可以通過組合來處理更加錯綜複雜的路由需求。Go語言目前沒有一個權威的web框架,就像Ruby語言有Rails和python有Django。這併不是説這樣的框架不存在,而是Go語言標準庫中的構建模塊就已經非常靈活以至於這些框架都是不必要的。此外,盡管在一個項目早期使用框架是非常方便的,但是它們帶來額外的複雜度會使長期的維護更加睏難。
|
||||
对于更复杂的应用,一些ServeMux可以通过组合来处理更加错综复杂的路由需求。Go语言目前没有一个权威的web框架,就像Ruby语言有Rails和python有Django。这并不是说这样的框架不存在,而是Go语言标准库中的构建模块就已经非常灵活以至于这些框架都是不必要的。此外,尽管在一个项目早期使用框架是非常方便的,但是它们带来额外的复杂度会使长期的维护更加困难。
|
||||
|
||||
在下面的程序中,我們創建一個ServeMux併且使用它將URL和相應處理/list和/price操作的handler聯繫起來,這些操作邏輯都已經被分到不同的方法中。然後我門在調用ListenAndServe函數中使用ServeMux最爲主要的handler。
|
||||
在下面的程序中,我们创建一个ServeMux并且使用它将URL和相应处理/list和/price操作的handler联系起来,这些操作逻辑都已经被分到不同的方法中。然后我门在调用ListenAndServe函数中使用ServeMux最为主要的handler。
|
||||
|
||||
<u><i>gopl.io/ch7/http3</i></u>
|
||||
```go
|
||||
@@ -144,15 +144,15 @@ func (db database) price(w http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
```
|
||||
|
||||
讓我們關註這兩個註冊到handlers上的調用。第一個db.list是一個方法值 (§6.4),它是下面這個類型的值
|
||||
让我们关注这两个注册到handlers上的调用。第一个db.list是一个方法值 (§6.4),它是下面这个类型的值
|
||||
|
||||
```go
|
||||
func(w http.ResponseWriter, req *http.Request)
|
||||
```
|
||||
|
||||
也就是説db.list的調用會援引一個接收者是db的database.list方法。所以db.list是一個實現了handler類似行爲的函數,但是因爲它沒有方法,所以它不滿足http.Handler接口併且不能直接傳給mux.Handle。
|
||||
也就是说db.list的调用会援引一个接收者是db的database.list方法。所以db.list是一个实现了handler类似行为的函数,但是因为它没有方法,所以它不满足http.Handler接口并且不能直接传给mux.Handle。
|
||||
|
||||
語句http.HandlerFunc(db.list)是一個轉換而非一個函數調用,因爲http.HandlerFunc是一個類型。它有如下的定義:
|
||||
语句http.HandlerFunc(db.list)是一个转换而非一个函数调用,因为http.HandlerFunc是一个类型。它有如下的定义:
|
||||
|
||||
<u><i>net/http</i></u>
|
||||
```go
|
||||
@@ -165,9 +165,9 @@ func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
|
||||
}
|
||||
```
|
||||
|
||||
HandlerFunc顯示了在Go語言接口機製中一些不同尋常的特點。這是一個有實現了接口http.Handler方法的函數類型。ServeHTTP方法的行爲調用了它本身的函數。因此HandlerFunc是一個讓函數值滿足一個接口的適配器,這里函數和這個接口僅有的方法有相同的函數籤名。實際上,這個技巧讓一個單一的類型例如database以多種方式滿足http.Handler接口:一種通過它的list方法,一種通過它的price方法等等。
|
||||
HandlerFunc显示了在Go语言接口机制中一些不同寻常的特点。这是一个有实现了接口http.Handler方法的函数类型。ServeHTTP方法的行为调用了它本身的函数。因此HandlerFunc是一个让函数值满足一个接口的适配器,这里函数和这个接口仅有的方法有相同的函数签名。实际上,这个技巧让一个单一的类型例如database以多种方式满足http.Handler接口:一种通过它的list方法,一种通过它的price方法等等。
|
||||
|
||||
因爲handler通過這種方式註冊非常普遍,ServeMux有一個方便的HandleFunc方法,它幫我們簡化handler註冊代碼成這樣:
|
||||
因为handler通过这种方式注册非常普遍,ServeMux有一个方便的HandleFunc方法,它帮我们简化handler注册代码成这样:
|
||||
|
||||
<u><i>gopl.io/ch7/http3a</i></u>
|
||||
```go
|
||||
@@ -175,11 +175,11 @@ mux.HandleFunc("/list", db.list)
|
||||
mux.HandleFunc("/price", db.price)
|
||||
```
|
||||
|
||||
從上面的代碼很容易看出應該怎麽構建一個程序,它有兩個不同的web服務器監聽不同的端口的,併且定義不同的URL將它們指派到不同的handler。我們隻要構建另外一個ServeMux併且在調用一次ListenAndServe(可能併行的)。但是在大多數程序中,一個web服務器就足夠了。此外,在一個應用程序的多個文件中定義HTTP handler也是非常典型的,如果它們必須全部都顯示的註冊到這個應用的ServeMux實例上會比較麻煩。
|
||||
从上面的代码很容易看出应该怎么构建一个程序,它有两个不同的web服务器监听不同的端口的,并且定义不同的URL将它们指派到不同的handler。我们只要构建另外一个ServeMux并且在调用一次ListenAndServe(可能并行的)。但是在大多数程序中,一个web服务器就足够了。此外,在一个应用程序的多个文件中定义HTTP handler也是非常典型的,如果它们必须全部都显示的注册到这个应用的ServeMux实例上会比较麻烦。
|
||||
|
||||
所以爲了方便,net/http包提供了一個全局的ServeMux實例DefaultServerMux和包級别的http.Handle和http.HandleFunc函數。現在,爲了使用DefaultServeMux作爲服務器的主handler,我們不需要將它傳給ListenAndServe函數;nil值就可以工作。
|
||||
所以为了方便,net/http包提供了一个全局的ServeMux实例DefaultServerMux和包级别的http.Handle和http.HandleFunc函数。现在,为了使用DefaultServeMux作为服务器的主handler,我们不需要将它传给ListenAndServe函数;nil值就可以工作。
|
||||
|
||||
然後服務器的主函數可以簡化成:
|
||||
然后服务器的主函数可以简化成:
|
||||
|
||||
<u><i>gopl.io/ch7/http4</i></u>
|
||||
```go
|
||||
@@ -191,8 +191,8 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
最後,一個重要的提示:就像我們在1.7節中提到的,web服務器在一個新的協程中調用每一個handler,所以當handler獲取其它協程或者這個handler本身的其它請求也可以訪問的變量時一定要使用預防措施比如鎖機製。我們後面的兩章中講到併發相關的知識。
|
||||
最后,一个重要的提示:就像我们在1.7节中提到的,web服务器在一个新的协程中调用每一个handler,所以当handler获取其它协程或者这个handler本身的其它请求也可以访问的变量时一定要使用预防措施比如锁机制。我们后面的两章中讲到并发相关的知识。
|
||||
|
||||
**練習 7.11:** 增加額外的handler讓客服端可以創建,讀取,更新和刪除數據庫記録。例如,一個形如 `/update?item=socks&price=6` 的請求會更新庫存清單里一個貨品的價格併且當這個貨品不存在或價格無效時返迴一個錯誤值。(註意:這個脩改會引入變量同時更新的問題)
|
||||
**练习 7.11:** 增加额外的handler让客服端可以创建,读取,更新和删除数据库记录。例如,一个形如 `/update?item=socks&price=6` 的请求会更新库存清单里一个货品的价格并且当这个货品不存在或价格无效时返回一个错误值。(注意:这个修改会引入变量同时更新的问题)
|
||||
|
||||
**練習 7.12:** 脩改/list的handler讓它把輸出打印成一個HTML的表格而不是文本。html/template包(§4.6)可能會對你有幫助。
|
||||
**练习 7.12:** 修改/list的handler让它把输出打印成一个HTML的表格而不是文本。html/template包(§4.6)可能会对你有帮助。
|
||||
|
||||
Reference in New Issue
Block a user