使用 Go 的庫非常容易實現(xiàn)一個 Web 服務(wù)器。
成都創(chuàng)新互聯(lián)公司專注于九江網(wǎng)站建設(shè)服務(wù)及定制,我們擁有豐富的企業(yè)做網(wǎng)站經(jīng)驗。 熱誠為您提供九江營銷型網(wǎng)站建設(shè),九江網(wǎng)站制作、九江網(wǎng)頁設(shè)計、九江網(wǎng)站官網(wǎng)定制、微信小程序定制開發(fā)服務(wù),打造九江網(wǎng)絡(luò)公司原創(chuàng)品牌,更為您提供九江網(wǎng)站排名全網(wǎng)營銷落地服務(wù)。
這是一個迷你服務(wù)器,返回訪問服務(wù)器的 URL 的路徑部分。例如,如果請求的 URL 是 http://localhost:8000/hello
,響應(yīng)將是 URL.Path= "/hello"
。
下面是完整程序的程序:
// 迷你回聲服務(wù)器
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
fmt.Println("http://localhost:8000/hello")
http.HandleFunc("/", handler) // 回聲請求調(diào)用處理程序
log.Fatal(http.ListenAndServe("localhost:8000", nil))
}
// 處理非持續(xù)回顯請求 URL r 的路徑部分
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path)
}
請求的 URL 的路徑就是 r.URL.Path
。
為服務(wù)器添加功能很容易。一個有用的擴展是一個特定的 URL,下面的版本對 /count 請求會有特殊的響應(yīng):
// 迷你回聲和計數(shù)器服務(wù)器
package main
import (
"fmt"
"log"
"net/http"
"sync"
)
var mu sync.Mutex
var count int
func main() {
fmt.Println("http://localhost:8000/hello")
http.HandleFunc("/", handler)
fmt.Println("http://localhost:8000/count")
http.HandleFunc("/count", counter)
log.Fatal(http.ListenAndServe("localhost:8000", nil))
}
// 處理程序回顯請求的 URL 的路徑部分
func handler(w http.ResponseWriter, r *http.Request) {
mu.Lock()
count++
fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path)
mu.Unlock()
}
// 回顯目前為止調(diào)用的次數(shù)
func counter(w http.ResponseWriter, r *http.Request) {
mu.Lock()
fmt.Fprintf(w, "Count %d\n", count)
mu.Unlock()
}
這個服務(wù)器有兩個處理函數(shù),通過請求的 URL 來決定哪一個被調(diào)用。
下面這個示例中的處理函數(shù),報告它接收到的請求頭和表單數(shù)據(jù),這樣還方便服務(wù)器審查和調(diào)試請求:
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
fmt.Println("http://localhost:8000/?k1=v1&k2=v2&k3=1&k3=2&k3=3")
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe("localhost:8000", nil))
}
// 處理程序回顯 HTTP 請求
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "%s %s %s\n", r.Method, r.URL, r.Proto)
for k, v := range r.Header {
fmt.Fprintf(w, "Header[%q] = %q\n", k, v)
}
fmt.Fprintf(w, "Host = %q\n", r.Host)
fmt.Fprintf(w, "RemoteAddr = %q\n", r.RemoteAddr)
if err := r.ParseForm(); err != nil {
log.Print(err)
}
for k, v := range r.Form {
fmt.Fprintf(w, "Form[%q] = %q\n", k, v)
}
}
這里匯報了很多的內(nèi)容:
進一步了解基于 http.Handler 接口的服務(wù)器API。
下面是源碼中接口的定義:
package http
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
ListenAndServe 函數(shù),這里關(guān)注接口,只看函數(shù)的簽名,忽略函數(shù)體的內(nèi)容。函數(shù)的第二個參數(shù)接收一個 Handler 接口的實例(用來接受所有的請求)。這個函數(shù)會一直執(zhí)行,直到服務(wù)出錯時返回一個非空的錯誤值。
下面的程序展示一個簡單的例子。使用map類型的database變量記錄商品和價格的映射。再加上一個 ServeHTTP 方法來滿足 http.Handler 接口。這個函數(shù)遍歷整個 map 并且輸出其中的元素:
package main
import (
"fmt"
"log"
"net/http"
)
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: %s\n", item, price)
}
}
func main() {
db := database{"shoes": 50, "socks": 5}
fmt.Println("http://localhost:8000")
log.Fatal(http.ListenAndServe("localhost:8000", db))
}
上面的示例中,服務(wù)器只能列出所有的商品,并且完全不管 URL,對每個請求都是同樣的功能。一般的 Web 服務(wù)會定義過個不同的 URL,每個觸發(fā)不同的行為。把現(xiàn)有的功能的 URL 設(shè)置為 /list,再加上另一個 /price 用來顯示單個商品的價格,商品可以在請求參數(shù)中指定,比如:/price?item=socks
:
package main
import (
"fmt"
"log"
"net/http"
)
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: %s\n", item, price)
}
case "/price":
item := req.URL.Query().Get("item")
price, ok := db[item]
if !ok {
w.WriteHeader(http.StatusNotFound) // 404
fmt.Fprintf(w, "no such item: %q\n", item)
// 也可以用 http.Error 實現(xiàn)上面2行的效果
// http.Error(w, fmt.Sprintf("no such item: %q\n", item), http.StatusNotFound)
return
}
fmt.Fprintf(w, "%s\n", price)
default:
w.WriteHeader(http.StatusNotFound) // 404
fmt.Fprintf(w, "no such page: %s\n", req.URL)
// http.Error(w, fmt.Sprintf("no such page: %s\n", req.URL), http.StatusNotFound)
}
}
func main() {
db := database{"shoes": 50, "socks": 5}
fmt.Println("http://localhost:8000/list")
fmt.Println("http://localhost:8000/price?item=shoes")
log.Fatal(http.ListenAndServe("localhost:8000", db))
}
現(xiàn)在,處理函數(shù)基于 URL 的路徑部分(req.URL.Path)來決定執(zhí)行哪部分邏輯。
返回錯誤頁面 404
如果處理函數(shù)不能識別這個路徑,那么它通過調(diào)用w.WriteHeader(http.StatusNotFound)
來返回一個 HTTP 錯誤。這個調(diào)用必須在網(wǎng) w 中寫入內(nèi)容之前執(zhí)行。這里還可以使用 http.Error 這個工具函數(shù)了達到同樣的目的:
msg := fmt.Sprintf("no such item: %q\n", item)
http.Error(w, msg, http.StatusNotFound) // 404
Get請求參數(shù)
對應(yīng) /price 的場景,它調(diào)用了 URL 的 Query 方法,把 HTTP 的請求參數(shù)解析為一個map,或者更精確來講,解析為一個 multimap,由 net/url 包的 url.Values 類型實現(xiàn)。這里的 url.Values 是一個 map 映射:
type Values map[string][]string
它的 value 是一個 字符串切片,這里用了 Get 方法,只會提取切片的第一個值。如果是要提取某個 key 所有的值,簡單的通過 map 的 key 提取 value 應(yīng)該就好了。
如果要繼續(xù)給 ServeHTTP 方法添加功能,應(yīng)當把每部分邏輯分到獨立的函數(shù)或方法。net/http 包提供了一個請求多工轉(zhuǎn)發(fā)器 ServeMux,用來簡化 URL 和處理程序之間的關(guān)聯(lián)。一個 ServeMux 把多個 http.Handler 組合成單個 http.Handler。在這里,可以看到滿足同一個接口的多個類型是可以互相替代的,Web 服務(wù)器可以把請求分發(fā)到任意一個 http.Handlr,而不用管后面具體的類型。
對于更加復雜的應(yīng)用,多個 ServeMux 會組合起來,用來處理更復雜的分發(fā)需求。Go 語言并不需要一個類似于 Python 的 Django 那樣的權(quán)威 Web 框架。因為 Go 語言的標準庫提供的基礎(chǔ)單元足夠靈活,以至于那樣的框架通常不是必須的。進一步來了講,盡管框架在項目初期帶來很多便利,但框架帶來了額外復雜性,增加長時間維護的難度。不過這樣的Web框架也是有的,比如:beego。
將程序修改為使用 ServeMux,用于將 /list、/prics 這樣的 URL 和對應(yīng)的處理程序關(guān)聯(lián)起來,這些處理程序也已經(jīng)拆分到不同的方法中。最后作為主處理程序在 ListenAndServe 調(diào)用中使用這個 ServeMux:
package main
import (
"fmt"
"log"
"net/http"
)
type dollars float32
func (d dollars) String() string { return fmt.Sprintf("$%.2f", d) }
type database map[string]dollars
func (db database) list(w http.ResponseWriter, req *http.Request) {
for item, price := range db {
fmt.Fprintf(w, "%s: %s\n", item, price)
}
}
func (db database) price(w http.ResponseWriter, req *http.Request) {
item := req.URL.Query().Get("item")
price, ok := db[item]
if !ok {
http.Error(w, fmt.Sprintf("no such item: %q\n", item), http.StatusNotFound)
return
}
fmt.Fprintf(w, "%s\n", price)
}
func main() {
db := database{"shoes": 50, "socks": 5}
fmt.Println("http://localhost:8000/list")
fmt.Println("http://localhost:8000/price?item=shoes")
mux := http.NewServeMux()
mux.Handle("/list", http.HandlerFunc(db.list))
mux.Handle("/price", http.HandlerFunc(db.price))
log.Fatal(http.ListenAndServe("localhost:8000", mux))
}
注冊處理程序
先關(guān)注一下用于注冊程序的兩次 mux.Handle 調(diào)用。在第一個調(diào)用中,db.list是一個方法值,即如下類型的一個值:
func(w http.ResponseWriter, req *http.Request)
當調(diào)用 db.list 時,等價于以 db 為接收者調(diào)用 database.list 方法。所以 db.list 是一個實現(xiàn)了處理功能的函數(shù)。然而他沒有接口所需的方法,所以它不滿足 http.Handler 接口,也不能直接傳給 mux.Handle。
表達式http.HandlerFunc(db.list)
其實是一個類型轉(zhuǎn)換,而不是函數(shù)調(diào)用。注意,http.HandlerFunc 是一個類型,它有如下定義:
package http
type HandlerFunc func(ResponseWriter, *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
http.HandlerFunc 這個函數(shù)類型它有自己的 ServeHTTP 方法,因此它滿足接口。而 http.HandlerFunc 的函數(shù)簽名和 db.list 這個方法值的函數(shù)簽名是一樣的,因此也能夠進行類型轉(zhuǎn)換。
這個是 Go 語言接口機制的一個不常見的特性。它不僅是一個函數(shù)類型,還可以擁有自己的方法,它的 ServeHTTP 方法就是調(diào)用函數(shù)本身,所以 HandlerFunc 是一個讓函數(shù)值滿足接口的一個適配器(關(guān)于適配器,我另外單獨寫了一篇:https://blog.51cto.com/steed/2392540 )。在這個例子里,函數(shù)和接口的唯一方法擁有同樣的簽名。這個小技巧讓 database 類型可以用不同的方式來滿足 http.Handler 接口,一次通過 list 方法,一次通過 price 方法。
因為這種注冊處理程序的方法太常見了,所以 ServeMux 引入了一個 HandleFunc 便捷方法來簡化調(diào)用,處理程序注冊部分的代碼可以簡化為如下的形式:
// mux.Handle("/list", http.HandlerFunc(db.list))
mux.HandleFunc("/list", db.list)
// mux.Handle("/prics", http.HandlerFunc(db.price))
mux.HandleFunc("/price", db.price)
全局 ServeMux 實例
通過 ServeMux,如果需要有兩個不同的 Web 服務(wù),在不同的端口監(jiān)聽。那么就定義不同的 URL,分發(fā)到不同的處理程序。只須簡單地構(gòu)造兩個 ServeMux,再調(diào)用一次 ListenAndServe 即可(建議并發(fā)調(diào)用)。不過很多時候一個 Web 服務(wù)足夠了,另外也不需要多個 ServeMux 實例。對于這種簡單的應(yīng)用場景,建議用下面的簡化的調(diào)用方法。
net/http 包還提供了一個全局的 ServeMux 實例 DefaultServeMux,以及包級別的注冊函數(shù) http.Handle 和 http.HandleFunc。要讓 DefaultServeMux 作為服務(wù)器的主處理程序,無須把它傳給 ListenAndServe,直接傳nil即可。文章開頭的例子里就是這么用的。
服務(wù)器的主函數(shù)可以進一步簡化:
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 服務(wù)器每次都用一個新的 goroutine 來調(diào)用處理程序,所以處理程序必須要注意并發(fā)問題。比如在訪問變量時的鎖問題,這個變量可能會被其他 goroutine 訪問,包括由同一個處理程序出廠的其他請求。文章開頭的第二個例子就要類似的處理。
并發(fā)安全是另外一塊內(nèi)容,需要單獨研究和解決,這里去簡單提一下。如果要添加創(chuàng)建、更新商品的功能,就需要注意并發(fā)安全。
功能需求
增加額外的處理程序,來支持創(chuàng)建、讀取、更新和刪除數(shù)據(jù)庫條目。比如,/update?item=socke&price=6
這樣的請求將更新倉庫中物品的價格,如果商品不存在或者價格無效就返回錯誤。(注意:這次修改會引入并發(fā)變量修改。)
Go 語言有兩種實現(xiàn)并發(fā)安全的方式,這里通過加鎖來保證并發(fā)安全:
package main
import (
"errors"
"fmt"
"log"
"net/http"
"strconv"
"sync"
)
type dollars float32
func (d dollars) String() string { return fmt.Sprintf("$%.2f", d) }
type database struct {
items map[string]dollars
sync.RWMutex
}
func (db *database) list(w http.ResponseWriter, req *http.Request) {
db.RLock()
defer db.RUnlock()
for item, price := range db.items {
fmt.Fprintf(w, "%s: %s\n", item, price)
}
}
func (db *database) price(w http.ResponseWriter, req *http.Request) {
item := req.URL.Query().Get("item")
db.RLock()
defer db.RUnlock()
price, ok := db.items[item]
if !ok {
http.Error(w, fmt.Sprintf("no such item: %q\n", item), http.StatusNotFound)
return
}
fmt.Fprintf(w, "%s\n", price)
}
// 從 URL 解析獲取item和price
func getItemPrice(req *http.Request) (string, dollars, error) {
item := req.URL.Query().Get("item")
if item == "" {
return "", 0, errors.New("item not get")
}
priceStr := req.URL.Query().Get("price")
if priceStr == "" {
return item, 0, errors.New("price not get")
}
price64, err := strconv.ParseFloat(priceStr, 32)
price := dollars(price64)
if err != nil {
return item, price, fmt.Errorf("Parse Price: %v\n", err)
}
return item, price, err
}
func (db *database) add(w http.ResponseWriter, req *http.Request) {
item, price, err := getItemPrice(req)
if err != nil {
http.Error(w, fmt.Sprintln(err), http.StatusNotFound)
return
}
db.Lock()
defer db.Unlock()
if _, ok := db.items[item]; ok {
http.Error(w, fmt.Sprintf("%s is already exist.\n", item), http.StatusNotFound)
return
}
db.items[item] = dollars(price)
fmt.Fprintf(w, "success add %s: %s\n", item, dollars(price))
}
func (db *database) update(w http.ResponseWriter, req *http.Request) {
item, price, err := getItemPrice(req)
if err != nil {
http.Error(w, fmt.Sprintln(err), http.StatusNotFound)
return
}
db.Lock()
defer db.Unlock()
if _, ok := db.items[item]; !ok {
http.Error(w, fmt.Sprintf("%s is not exist.\n", item), http.StatusNotFound)
return
}
db.items[item] = dollars(price)
fmt.Fprintf(w, "success udate %s: %s\n", item, dollars(price))
}
func (db *database) delete(w http.ResponseWriter, req *http.Request) {
item := req.URL.Query().Get("item")
func () {
db.Lock()
defer db.Unlock()
delete(db.items, item)
}()
db.list(w, req)
}
func main() {
db := database{
items: map[string]dollars{"shoes": 50, "socks": 5},
}
fmt.Println("http://localhost:8000/list")
fmt.Println("http://localhost:8000/price?item=shoes")
fmt.Println("http://localhost:8000/add?item=football&price=11")
fmt.Println("http://localhost:8000/update?item=football&price=12.35")
fmt.Println("http://localhost:8000/delete?item=shoes")
http.HandleFunc("/list", db.list)
http.HandleFunc("/price", db.price)
http.HandleFunc("/add", db.add)
http.HandleFunc("/update", db.update)
http.HandleFunc("/delete", db.delete)
log.Fatal(http.ListenAndServe("localhost:8000", nil))
}
不但新增的創(chuàng)建、更新和刪除的方法要加鎖,因為現(xiàn)在有了并發(fā)安全問題,原本的讀取方法也需要加鎖,才能保證讀取到的數(shù)據(jù)是當前最新的。
這部分內(nèi)容是從別處收集來了。
Go 語言原生支持 http,所有 Go 的http服務(wù)性能和nginx比較接近。如果用 Go 寫的 Web 程序上線,程序前面不需要再部署nginx的Web服務(wù)器,這樣就省掉的是Web服務(wù)器。這是單應(yīng)用的部署。
對于多應(yīng)用部署,服務(wù)器需要部署多個Web應(yīng)用,這時就需要反向代理了,一般這也是nginx或apache。
反向代理,有個很棒的說法是流量轉(zhuǎn)發(fā)。我獲取到客戶端來的請求,將它發(fā)往另一個服務(wù)器,從服務(wù)器獲取到響應(yīng)再回給原先的客戶端。反向的意義簡單來說在于這個代理自身決定了何時將流量發(fā)往何處。
Go 的反向代理,可以參考下這篇。1 行 Go 代碼實現(xiàn)反向代理:
https://studygolang.com/articles/14246
下面是我之前寫的另一篇有個 HTTP 服務(wù)端內(nèi)容的,主要是這篇里的Panic 處理這個小章節(jié),讓程序可以在處理函數(shù)發(fā)生崩潰之后可以通過 revoer 來自動恢復:
https://blog.51cto.com/steed/2321827
網(wǎng)頁標題:GoWeb服務(wù)
文章位置:http://muchs.cn/article36/picssg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供Google、網(wǎng)站收錄、營銷型網(wǎng)站建設(shè)、域名注冊、自適應(yīng)網(wǎng)站、面包屑導航
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)