Go實現HTTP服務端


  • 使用 Go 語言標准庫 http 搭建 HTTP Server
  • 並實現 main 函數啟動 HTTP Server 測試 API

Go 語言提供了 http 標准庫,可以非常方便地搭建 HTTP 服務端和客戶端。比如我們可以實現一個服務端,無論接收到什么請求,都返回字符串 “Hello World!”

package main

import (
	"log"
	"net/http"
)

type server int

func (h *server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	log.Println(r.URL.Path)
	w.Write([]byte("Hello World!"))
}

func main() {
	var s server
	http.ListenAndServe("localhost:9999", &s)
}

  

  • 創建任意類型 server,並實現 ServeHTTP 方法。
  • 調用 http.ListenAndServe 在 9999 端口啟動 http 服務,處理請求的對象為 s server

執行 go run . 啟動服務,借助 curl 來測試效果:

$ curl http://localhost:9999  
Hello World!
$ curl http://localhost:9999/abc
Hello World!


http.ListenAndServe 接收 2 個參數,第一個參數是服務啟動的地址,第二個參數是 Handler,任何實現了 ServeHTTP 方法的對象都可以作為 HTTP 的 Handler。

在標准庫中,http.Handler 接口的定義如下:

package http

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

  

分布式緩存需要實現節點間通信,建立基於 HTTP 的通信機制是比較常見和簡單的做法。如果一個節點啟動了 HTTP 服務,那么這個節點就可以被其他節點訪問。今天為單機節點搭建 HTTP Server。

將這部分代碼放在新的 http.go 文件中,當前的代碼結構如下:

geecache/
|--lru/
|--lru.go // lru 緩存淘汰策略
|--byteview.go // 緩存值的抽象與封裝
|--cache.go // 並發控制
|--geecache.go // 負責與外部交互,控制緩存存儲和獲取的主流程
|--http.go // 提供被其他節點訪問的能力(基於http)


創建一個結構體 HTTPPool,作為承載節點間 HTTP 通信的核心數據結構(包括服務端和客戶端,只實現服務端)。
package geecache

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

const defaultBasePath = "/_geecache/"

// HTTPPool implements PeerPicker for a pool of HTTP peers.
type HTTPPool struct {
	// this peer's base URL, e.g. "https://example.net:8000"
	self     string
	basePath string
}

// NewHTTPPool initializes an HTTP pool of peers.
func NewHTTPPool(self string) *HTTPPool {
	return &HTTPPool{
		self:     self,
		basePath: defaultBasePath,
	}
}

  

  • HTTPPool 只有 2 個參數,一個是 self,用來記錄自己的地址,包括主機名/IP 和端口。
  • 另一個是 basePath,作為節點間通訊地址的前綴,默認是 /_geecache/,那么 http://example.com/_geecache/ 開頭的請求,就用於節點間的訪問。因為一個主機上還可能承載其他的服務,加一段 Path 是一個好習慣。比如,大部分網站的 API 接口,一般以 /api 作為前綴。

實現最為核心的 ServeHTTP 方法:

func (p *HTTPPool) Log(format string, v ...interface{}) {
	log.Printf("[Server %s] %s", p.self, fmt.Sprintf(format, v...))
}

// ServeHTTP handle all http requests
func (p *HTTPPool) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if !strings.HasPrefix(r.URL.Path, p.basePath) {
		panic("HTTPPool serving unexpected path: " + r.URL.Path)
	}
	p.Log("%s %s", r.Method, r.URL.Path)
	// /<basepath>/<groupname>/<key> required
	parts := strings.SplitN(r.URL.Path[len(p.basePath):], "/", 2)
	if len(parts) != 2 {
		http.Error(w, "bad request", http.StatusBadRequest)
		return
	}

	groupName := parts[0]
	key := parts[1]

	group := GetGroup(groupName)
	if group == nil {
		http.Error(w, "no such group: "+groupName, http.StatusNotFound)
		return
	}

	view, err := group.Get(key)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/octet-stream")
	w.Write(view.ByteSlice())
}

  

  • ServeHTTP 的實現邏輯是比較簡單的,首先判斷訪問路徑的前綴是否是 basePath,不是返回錯誤。
  • 我們約定訪問路徑格式為 /<basepath>/<groupname>/<key>,通過 groupname 得到 group 實例,再使用 group.Get(key) 獲取緩存數據。
  • 最終使用 w.Write() 將緩存值作為 httpResponse 的 body 返回。

在單機上啟動 HTTP 服務,使用 curl 進行測試

package main

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

var db = map[string]string{
	"Tom":  "630",
	"Jack": "589",
	"Sam":  "567",
}

func main() {
	geecache.NewGroup("scores", 2<<10, geecache.GetterFunc(
		func(key string) ([]byte, error) {
			log.Println("[SlowDB] search key", key)
			if v, ok := db[key]; ok {
				return []byte(v), nil
			}
			return nil, fmt.Errorf("%s not exist", key)
		}))

	addr := "localhost:9999"
	peers := geecache.NewHTTPPool(addr)
	log.Println("geecache is running at", addr)
	log.Fatal(http.ListenAndServe(addr, peers))
}

  

  • 同樣地,我們使用 map 模擬了數據源 db。
  • 創建一個名為 scores 的 Group,若緩存為空,回調函數會從 db 中獲取數據並返回。
  • 使用 http.ListenAndServe 在 9999 端口啟動了 HTTP 服務。

需要注意的點:
main.go 和 geecache/ 在同級目錄,但 go modules 不再支持 import <相對路徑>,相對路徑需要在 go.mod 中聲明:
require geecache v0.0.0
replace geecache => ./geecache

運行 main 函數,使用 curl 做一些簡單測試:

$ curl http://localhost:9999/_geecache/scores/Tom
630
$ curl http://localhost:9999/_geecache/scores/kkk
kkk not exist


節點間的相互通信不僅需要 HTTP 服務端,還需要 HTTP 客戶端


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM