fasthttp 概述與 Hello World(本文)
> fasthttp 文章系列: > * fasthttp 概述與 Hello World(本文) > * fasthttp 客戶端與服務端的封裝, 日誌與路由 > * fasthttp 所謂 RESTful (兼介紹 fastjson) > * fasthttp 中介軟體 ( 簡單認證/ session 會話...) > * fasthttp 處理 JWT (及 JWT 安全性) > * fasthttp 對接非標準 web client (作為 AAA, 資料加解密) > * fasthttp 快取/proxy 代理/反向代理 > * fasthttp 部署
[簡述] github.com/valyala/fasthttp 是 golang 中一個標誌性的高效能 HTTP 庫, 主要用於 webserver 開發, 以及 web client / proxy 等. fasthttp 的高效能開發思路, 啟發了很多開發者.
fasthttp 自己的介紹如下:
> Fast HTTP package for Go. Tuned for high performance. Zero memory allocations in hot paths. Up to 10x faster than net/http > > Fast HTTP implementation for Go. > > > Currently fasthttp is successfully used by VertaMedia > in a production serving up to 200K rps from more than 1.5M concurrent keep-alive > connections per physical server.
事實上, 這有點小誇張, 但在一定場景下經過優化部署, 確是有很高的效能.
近 3 年來, fasthttp 被我用在幾個重大專案 (對我而言, 專案有多重大, 與收錢的多少成正比) 中, 這裡, 就寫一個小系列, 介紹 fasthttp 的實際使用與經驗得失.
> -------------------------------------- > > 想直接看程式碼的朋友, 請訪問 我寫的 fasthttp-example > > ------------------------------------------
0. 關於 fasthttp 的優點介紹
以下文字來自 傅小黑 原創文章: Go 開發 HTTP 的另一個選擇 fasthttp 寫於 2016/09/30 :
> fasthttp 是 Go 的一款不同於標準庫 net/http 的 HTTP 實現。fasthttp 的效能可以達到標準庫的 10 倍,說明他魔性的實現方式。主要的點在於四個方面: > > * net/http 的實現是一個連線新建一個 goroutine;fasthttp 是利用一個 worker 複用 goroutine,減輕 runtime 排程 goroutine 的壓力 > * net/http 解析的請求資料很多放在 map[string] string(http.Header) 或 map[string][] string(http.Request.Form),有不必要的 [] byte 到 string 的轉換,是可以規避的 > * net/http 解析 HTTP 請求每次生成新的 *http.Request 和 http.ResponseWriter; fasthttp 解析 HTTP 資料到 *fasthttp.RequestCtx,然後使用 sync.Pool 複用結構例項,減少物件的數量 > * fasthttp 會延遲解析 HTTP 請求中的資料,尤其是 Body 部分。這樣節省了很多不直接操作 Body 的情況的消耗 > > 但是因為 fasthttp 的實現與標準庫差距較大,所以 API 的設計完全不同。使用時既需要理解 HTTP 的處理過程,又需要注意和標準庫的差別。
這段文字非常精練的總結了 fasthttp 的特點, 我摘錄了這部分放在這裡, 感謝 傅小黑 --- 另外, 傅小黑 的技術文章非常棒, 歡迎大家去圍觀他....
1. 從 HTTP 1.x 協議說起
> 想要使用 fasthttp 的朋友, 請儘量對 http 1.x 協議要很熟悉, 很熟悉.
1.1 HTTP 1.x 協議簡述
簡單來說, HTTP 1.x 協議, 是一個被動式短連線的 client (請求 request ) - server ( 響應 response) 互動的規範:
-
協議一般來說, 以 TCP 通訊協議為基礎 ( 不談 QUIC 這個以 udp 為底層實現的變異)
> web client 通過 DNS 把域名轉換成 IP 地址後, 與 web server 握手連線, 連線成功後, web client 客戶端向 web server 服務端發出請求, 服務端收到請求後, 向 client 客戶端應答
通過 URL / URI 進行導址, 同時, URL/URI 中包含部分資料 > URL 形式如 http://192.168.1.1:8088/rpc/schedule > 其中 http://192.168.1.1:8080 這部分是通訊協議, 伺服器 IP 地址與埠號, 這是前面 TCP 通訊的依據 > 1. web 伺服器端在 http://192.168.1.1:8080 這個地址上監聽, 隨時準備接收 web client 請求並應答 > 1. web 客戶端通過 http://192.168.1.1:8080 這個地址所指定的 web 伺服器進行 tcp 連線, 連線成功後, web 客戶端向伺服器發出 請求資料, web 服務端應答 響應資料 > 1. 特別注意, 請求資料, 與響應資料, 遵從 HTTP 協議規定的統一格式
在 HTTP 1.x 協議中規定的傳輸 ( 請求/應答) 資料格式, 一般稱為 HyperText, 是一種文字資料格式, 當然了, 在 TCP 傳輸時還是二進位制資料塊 ( 這是使用 fasthttp 的關鍵點) . 具體資料格式見 1.2 小節
HTTP 協議規定了一些信令, 如下描述, 來區分不同的互動操作 > 根據 HTTP 標準,HTTP 請求可以使用多種請求方法: > > * HTTP1.0 定義了三種請求方法: GET, POST 和 HEAD 方法。 > * HTTP1.1 新增了五種請求方法:OPTIONS, PUT, DELETE, TRACE 和 CONNECT 方法。
由於 HTTP 協議相關的 MIME 規範, HTTP 1.x 也可以傳輸影像/音樂/視訊等其他資料格式,但這些被傳輸的真正有效資料都被封裝在 http payload 這一部分裡, http header 還保留 ( 只是欄位多少, 以及欄位中的值不同) ---------這是另一個與 fasthttp 關聯的另一個要點
1.2 HTTP 1.x 中的請求/響應共同遵從的資料格式
下面看一個 POST 請求
請求資料如下, 響應是一樣的格式. 在下面的資料中:
1. 在下面的資料格式中, 特別注意, 中間有一個空行 1. 空行上半部分, 叫 http header , 下半部分, 叫 http payload 或叫 http body 1. 在上半部分的 http header 中, 請注意第 1,2 行 1. 請對比一下, 下方同時列出的 GET 請求資料
POST 請求資料示例
POST /rpc/schedule HTTP/1.1
Host: 192.168.1.1:3001
Content-Type: application/json
Accept: application/vnd.pgrst.object+json
User-Agent: PostmanRuntime/7.15.2
Host: 192.168.1.1:3001
Accept-Encoding: gzip, deflate
Content-Length: 208
Connection: keep-alive
{
"actual_start_date": "2019-07-29",
"actual_end_date": "2019-07-29",
"plan_start_date": "2019-07-29",
"plan_end_date": "2019-02-12",
"title": "00002",
"user_id": 2098735545843717147
}
GET 請求示例
GET /schedule?user_id=eq.2098735545843717147 HTTP/1.1
Host: 192.168.1.1:3001
Content-Type: application/json
User-Agent: PostmanRuntime/7.15.2
Accept: */*
Host: 192.168.1.1:3001
Accept-Encoding: gzip, deflate
Content-Length: 208
Connection: keep-alive
{
"actual_start_date": "2019-07-29",
"actual_end_date": "2019-07-29",
"plan_start_date": "2019-07-29",
"plan_end_date": "2019-02-12",
"title": "00002",
"user_id": 2098735545843717147
}
1.3 http 1.x 協議小結與開發關聯點
這裡幾句很重要, 所以,
HTTP 1.x 幾個基礎點:
- HTTP 1.x 通過 tcp 進行通訊
- 請求與響應的格式, 資料資料的格式是一樣的 > 特別注意請求資料中的第一行,第二行 > 特別注意 HTTP header 與 HTTP payload 的那空行分隔
- 注意 URL/URI 中也包含有資料, 換個話說,在 http://192.168.1.1:3001/schedule?user_id=eq.2098735545843717147 中, 其他部分 /schedule?user_id=eq.2098735545843717147 看做請求資料的一部分
從 HTTP 1.x 協議, 可以總結 web 開發的要點
- 處理 tcp 通訊, 包括: > * 通過 dns 轉化域名得到 IP 地址, 包括 ip4 / ip6 地址 > * 對 tcp 進行通訊重寫或優化, 長連線或短連線, 都在這裡了 > * 或對 tcp 進行轉發 ( 這是 proxy ) 或劫持, 在 tcp 通訊最底層進行一些特殊操作
- 對 URL /URI 進行處理, 這是路由定址 > * 按 URI 及相關資料特徵進行攔截處理, 這是反向代理與快取 > * 進行一些 URI 轉換, 例如 302 的重定向 > * 在 URI 中攜帶小部分資料的組裝與處理
-
HTTP 資料處理
> * 對 HTTP header / HTTP payload 進行處理, 這是變化最多的部分, 按業務/功能的不同, 即簡單也複雜
fasthttp 的效能優化思路
- 重寫了在 tcp 之上進行 HTTP 握手/連線/通訊的 goroutine pool 實現
- 對 http 資料基本按傳輸時的二進位制進行延遲處理, 交由開發者按需決定
- 對二進位制的資料進行了快取池處理, 需要開發者手工處理以達到零記憶體分配
> -------------------------------------- > > 好, HTTP 1.x 就簡述到這了, 後面會大量引用到這一章節說的內容. > > -------------------------------------- >
2. fasthttp 非"標準"的爭議, 及為什麼選擇 fasthttp
這一章節暫時不寫了, 需要一點時間進行調整
3. 開發環境及建立 project
個人主力用 MacOS 開發, 以下就以 MacOS 為例
3.1. go 安裝, 環境變數及 goproxy 配置
下載 golang 編譯器並安裝
下載地址為 https://dl.google.com/go/go1.12.7.darwin-amd64.pkg
任意下載到一個路徑下後, 雙擊安裝
或者, 開啟一個 Terminal 命令列終端
cd ~
mkdir -p ~/go/src/github.com/tsingson/fasthttp-example/hello-world
cd ~/go/src/github.com/fasthttp-example
wget https://dl.google.com/go/go1.12.7.darwin-amd64.pkg
open ./go1.12.7.darwin-amd64.pkg
出現 go 的安裝介面後, 一路確認就安裝完成了
配置環境變數
由於我的 MacOS 已經使用 zshell , 所以, 預設全域性環境變數在 ~/.zshrc
開啟 ~/.zshrc 加入以下文字
export GOBIN=/Users/qinshen/go/bin #/Users/qinshen 這是我的個人帳號根路徑
export GOSUMDB=off
export GOPATH="/Users/qinshen/go"
export GOCACHE="/Users/qinshen/go/pkg/cache"
export GO111MODULE=on
export CGO_ENABLED=1
# export GOPROXY=http://127.0.0.1:3000 # 這一行是本機在 docker 中執行 athens 這個 goproxy
# export GOPROXY=https://athens.azurefd.net #遠端 ahtens goproxy
# export GOPROXY=direct # 如果能直接訪問 golang.org, 那就用這個配置
export GOPROXY=https://goproxy.cn #中國大陸, 用這個吧
# export GOPROXY=https://proxy.golang.org # go1.13 推薦的 goproxy, 試手用
export PATH=$PATH:$GOROOT:$GOBIN
export PS1='%d '
驗證 go 安裝
cd ~/go/src/github.com/fasthttp-example
touch ./hello-world/main.go
用一個文字編輯器如 sublime text 3 , 對 ~/go/src/github.com/fasthttp-example/hello-world/main.go 寫入以下 go 程式碼
package main
import (
"fmt"
)
func main() {
fmt.Println("Hello World, 中國...")
}
執行驗證
/Users/qinshen/go/src/github.com/tsingson/fasthttp-example go run ./hello-world
Hello World, 中國...
/Users/qinshen/go/src/github.com/tsingson/fasthttp-example
done. 完成.
3.2 建立基於 go module 的 project
在專案路徑在 go/src/github/tsingson/fasthttp-example 下, 直接執行 或 go mod init
如果專案路徑在任意路徑下, 例如在 ~/go/fasthttp-example 下, 則執行 go mod init github.com/tsingson/fasthttp-example
以下是執行結果, 及專案結構
/Users/qinshen/go/src/github.com/tsingson/fasthttp-example go mod init github.com/tsingson/fasthttp-example
go: creating new go.mod: module github.com/tsingson/fasthttp-example
/Users/qinshen/go/src/github.com/tsingson/fasthttp-example tree .
.
├── README.md
├── cmd
│ ├── test-client
│ │ └── main.go
│ └── test-server
│ └── main.go
├── go.mod
├── hello-world
│ └── main.go
├── webclient
│ └── client.go
└── webserver
├── config.go
├── const.go
├── handler.go
├── middleware.go
├── router.go
├── server.go
└── testHandler.go
6 directories, 13 files
/Users/qinshen/go/src/github.com/tsingson/fasthttp-example
3.3 Hello World 單機版
好了, 把 hello-world/main.go 改成以下程式碼
package main
import (
"fmt"
"os"
)
func main() {
var who = "中國"
if len(os.Args[1]) > 0 {
who = os.Args[1]
}
fmt.Println("Hello World, ", who)
}
執行一下
/Users/qinshen/go/src/github.com/tsingson/fasthttp-example go run ./hello-world/main.go tsingson
Hello World, tsingson
/Users/qinshen/go/src/github.com/tsingson/fasthttp-example go run ./hello-world/main.go 三明智
Hello World, 三明智
/Users/qinshen/go/src/github.com/tsingson/fasthttp-example
很好, 我們得到了一個單機版, 命令列方式的 hello world.
下面, 我們把 hello world 改成 fasthttp web 版本...............
4. 選用 usber-go/zap 日誌並簡單封裝
匯入 uber 的 zap 日誌庫
/Users/qinshen/go/src/github.com/tsingson/fasthttp-example go get go.uber.org/zap
建立封裝的日誌庫
/Users/qinshen/go/src/github.com/tsingson/fasthttp-example touch ./logger/zap.go
寫入以下程式碼
package logger
import (
"os"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
// NewConsoleDebug new zap logger for console
func NewConsoleDebug() zapcore.Core {
// First, define our level-handling logic.
highPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl >= zapcore.ErrorLevel
})
lowPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl < zapcore.ErrorLevel
})
// High-priority output should also go to standard error, and low-priority
// output should also go to standard out.
consoleDebugging := zapcore.Lock(os.Stdout)
consoleErrors := zapcore.Lock(os.Stderr)
// Optimize the console output for human operators.
consoleEncoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig())
// Join the outputs, encoders, and level-handling functions into
// zapcore.Cores, then tee the four cores together.
var stderr = zapcore.NewCore(consoleEncoder, consoleErrors, highPriority)
var stdout = zapcore.NewCore(consoleEncoder, consoleDebugging, lowPriority)
return zapcore.NewTee(stderr, stdout)
}
// ConsoleWithStack console log for debug
func ConsoleWithStack() *zap.Logger {
core := NewConsoleDebug()
// From a zapcore.Core, it's easy to construct a Logger.
return zap.New(core).WithOptions(zap.AddCaller())
}
// Console console log for debug
func Console() *zap.Logger {
core := NewConsoleDebug()
// From a zapcore.Core, it's easy to construct a Logger.
return zap.New(core)
}
5. 寫一個 fasthttp 版本的 Hello World
5.1 專案結構
/Users/qinshen/go/src/github.com/tsingson/fasthttp-example tree .
.
├── README.md
├── cmd
│ ├── test-client
│ │ └── main.go
│ └── test-server
│ └── main.go
├── go.mod
├── go.sum
├── hello-web
│ ├── hello-client
│ │ └── main.go
│ └── hello-server
│ └── main.go
├── hello-world
│ └── main.go
├── logger
│ └── zap.go
├── webclient
│ └── client.go
└── webserver
├── config.go
├── const.go
├── handler.go
├── middleware.go
├── router.go
├── server.go
└── testHandler.go
10 directories, 17 files
/Users/qinshen/go/src/github.com/tsingson/fasthttp-example
5.2 fasthttp Hello World 服務端
直接上程式碼
package main
import (
"bytes"
"strconv"
"github.com/savsgio/gotils"
"github.com/valyala/fasthttp"
"go.uber.org/zap"
"github.com/tsingson/fasthttp-example/logger"
)
func main() {
var log *zap.Logger = logger.Console()
var address = "127.0.0.1:3001"
// -------------------------------------------------------
// fasthttp 的 handler 處理函式
// -------------------------------------------------------
var requestHandler = func(ctx *fasthttp.RequestCtx) {
// -------------------------------------------------------
// 處理 web client 的請求資料
// -------------------------------------------------------
// 取出 web client 請求進行 TCP 連線的連線 ID
var connID = strconv.FormatUint(ctx.ConnID(), 10)
// 取出 web client 請求 HTTP header 中的事務ID
var tid = string( ctx.Request.Header.PeekBytes([]byte("TransactionID")))
if len(tid) == 0 {
tid = "12345678"
}
log.Debug("HTTP 訪問 TCP 連線 ID " + connID)
// 取出 web 訪問的 URL/URI
var uriPath = ctx.Path()
{
// 取出 URI
log.Debug("---------------- HTTP URI -------------")
log.Debug(" HTTP 請求 URL 原始資料 > ", zap.String("request", ctx.String()))
}
// 取出 web client 請求的 URL/URI 中的引數部分
{
log.Debug("---------------- HTTP URI 引數 -------------")
var uri = ctx.URI().QueryString()
log.Debug("在 URI 中的原始資料 > " + string(uri))
log.Debug("---------------- HTTP URI 每一個鍵值對 -------------")
ctx.URI().QueryArgs().VisitAll(func(key, value []byte) {
log.Debug(tid, zap.String("key", gotils.B2S(key)), zap.String("value", gotils.B2S(value)))
})
}
// -------------------------------------------------------
// 注意對比一下, 下面的程式碼段, 與 web client 中幾乎一樣
// -------------------------------------------------------
{
// 取出 web client 請求中的 HTTP header
{
log.Debug("---------------- HTTP header 每一個鍵值對-------------")
ctx.Request.Header.VisitAll(func(key, value []byte) {
// l.Info("requestHeader", zap.String("key", gotils.B2S(key)), zap.String("value", gotils.B2S(value)))
log.Debug(tid, zap.String("key", gotils.B2S(key)), zap.String("value", gotils.B2S(value)))
})
}
// 取出 web client 請求中的 HTTP payload
{
log.Debug("---------------- HTTP payload -------------")
log.Debug(tid, zap.String("http payload", gotils.B2S(ctx.Request.Body())))
}
}
switch {
// 如果訪問的 URI 路由是 /uri 開頭 , 則進行下面這個響應
case len(uriPath) > 1:
{
log.Debug("---------------- HTTP 響應 -------------")
// -------------------------------------------------------
// 處理邏輯開始
// -------------------------------------------------------
// payload 是 []byte , 是 web response 返回的 HTTP payload
var payload = bytes.NewBuffer([]byte("Hello, "))
// 這是從 web client 取資料
var who = ctx.QueryArgs().PeekBytes([]byte("who"))
if len(who) > 0 {
payload.Write(who)
} else {
payload.Write([]byte(" 中國 "))
}
// -------------------------------------------------------
// 處理 HTTP 響應資料
// -------------------------------------------------------
// HTTP header 構造
ctx.Response.Header.SetStatusCode(200)
ctx.Response.Header.SetConnectionClose() // 關閉本次連線, 這就是短連線 HTTP
ctx.Response.Header.SetBytesKV([]byte("Content-Type"), []byte("text/plain; charset=utf8"))
ctx.Response.Header.SetBytesKV([]byte("TransactionID"), []byte(tid))
// HTTP payload 設定
// 這裡 HTTP payload 是 []byte
ctx.Response.SetBody(payload.Bytes())
}
// 訪問路踴不是 /uri 的其他響應
default:
{
log.Debug("---------------- HTTP 響應 -------------")
// -------------------------------------------------------
// 處理邏輯開始
// -------------------------------------------------------
// payload 是 []byte , 是 web response 返回的 HTTP payload
var payload = bytes.NewBuffer([]byte("Hello, "))
// 這是從 web client 取資料
var who = ctx.QueryArgs().PeekBytes([]byte("who"))
if len(who) > 0 {
payload.Write(who)
} else {
payload.Write([]byte(" 中國 "))
}
// -------------------------------------------------------
// 處理 HTTP 響應資料
// -------------------------------------------------------
// HTTP header 構造
ctx.Response.Header.SetStatusCode(200)
ctx.Response.Header.SetConnectionClose() // 關閉本次連線, 這就是短連線 HTTP
ctx.Response.Header.SetBytesKV([]byte("Content-Type"), []byte("text/plain; charset=utf8"))
ctx.Response.Header.SetBytesKV([]byte("TransactionID"), []byte(tid))
// HTTP payload 設定
// 這裡 HTTP payload 是 []byte
ctx.Response.SetBody(payload.Bytes())
}
}
return
}
// -------------------------------------------------------
// 建立 fasthttp 伺服器
// -------------------------------------------------------
// Create custom server.
s := &fasthttp.Server{
Handler: requestHandler, // 注意這裡
Name: "hello-world server", // 伺服器名稱
}
// -------------------------------------------------------
// 執行服務端程式
// -------------------------------------------------------
log.Debug("------------------ fasthttp 伺服器嘗試啟動------ ")
if err := s.ListenAndServe(address); err != nil {
log.Fatal("error in ListenAndServe", zap.Error(err))
}
}
5.3 fasthttp Hello World 客戶端 ( GET )
看程式碼
package main
import (
"net/url"
"os"
"time"
"github.com/savsgio/gotils"
"github.com/valyala/fasthttp"
"go.uber.org/zap"
"github.com/tsingson/fasthttp-example/logger"
)
func main() {
var log *zap.Logger = logger.Console()
var baseURL = "http://127.0.0.1:3001"
// 隨便指定一個字串做為 web 請求的事務ID , 用來列印多條日誌時, 區分是否來自同一個 web 請求事務
var tid = "12345678"
// -------------------------------------------------------
// 構造 web client 請求的 URL
// -------------------------------------------------------
var fullURL string
{
relativeUrl := "/uri/"
u, err := url.Parse(relativeUrl)
if err != nil {
log.Fatal("error", zap.Error(err))
}
queryString := u.Query()
// 這裡構造 URI 中的資料, 每一個鍵值對
{
queryString.Set("id", "1")
queryString.Set("who", "tsingson")
queryString.Set("where", "中國深圳")
}
u.RawQuery = queryString.Encode()
base, err := url.Parse(baseURL)
if err != nil {
log.Fatal("error", zap.Error(err))
os.Exit(-1)
}
fullURL = base.ResolveReference(u).String()
log.Debug("---------------- HTTP 請求 URL -------------")
log.Debug(tid, zap.String("http request URL > ", fullURL))
}
// -------------------------------------------------------
// fasthttp web client 的初始化, 與清理
// -------------------------------------------------------
// fasthttp 從快取池中申請 request / response 物件
var req = fasthttp.AcquireRequest()
var resp = fasthttp.AcquireResponse()
// 釋放申請的物件到池中
defer func() {
fasthttp.ReleaseResponse(resp)
fasthttp.ReleaseRequest(req)
}()
// -------------------------------------------------------
// 構造 web client 請求資料
// -------------------------------------------------------
// 指定 HTTP 請求的 URL
req.SetRequestURI(fullURL)
// 指定 HTTP 請求的方法
req.Header.SetMethod("GET")
// 設定 HTTP 請求的 HTTP header
req.Header.SetBytesKV([]byte("Content-Type"), []byte("text/plain; charset=utf8"))
req.Header.SetBytesKV([]byte("User-Agent"), []byte("fasthttp-example web client"))
req.Header.SetBytesKV([]byte("Accept"), []byte("text/plain; charset=utf8"))
req.Header.SetBytesKV([]byte("TransactionID"), []byte(tid))
// 設定 web client 請求的超時時間
var timeOut = 3 * time.Second
// 計時開始
t1 := time.Now()
// DO request
var err = fasthttp.DoTimeout(req, resp, timeOut)
if err != nil {
log.Error("post request error", zap.Error(err))
os.Exit(-1)
}
// -------------------------------------------------------
// 處理返回結果
// -------------------------------------------------------
elapsed := time.Since(t1)
log.Debug("---------------- HTTP 響應消耗時間-------------")
log.Debug(tid, zap.Duration("elapsed", elapsed))
log.Debug("---------------- HTTP 響應狀態碼 -------------")
log.Debug(tid, zap.Int("http status code", resp.StatusCode()))
log.Debug("---------------- HTTP 響應 header 與 payload -------------")
// -------------------------------------------------------
// 注意對比一下, 下面的程式碼段, 與 web server 中幾乎一樣
// -------------------------------------------------------
{
// 取出 web client 請求中的 HTTP header
{
log.Debug("---------------- HTTP header 每一個鍵值對-------------")
resp.Header.VisitAll(func(key, value []byte) {
// l.Info("requestHeader", zap.String("key", gotils.B2S(key)), zap.String("value", gotils.B2S(value)))
log.Debug(tid, zap.String("key", gotils.B2S(key)), zap.String("value", gotils.B2S(value)))
})
}
// 取出 web client 請求中的 HTTP payload
{
log.Debug("---------------- HTTP payload -------------")
log.Debug(tid, zap.String("http payload", gotils.B2S(resp.Body())))
}
}
}
5.4 編譯與執行
編譯
/Users/qinshen/go/src/github.com/tsingson/fasthttp-example go install ./hello-web/...
/Users/qinshen/go/src/github.com/tsingson/fasthttp-example
執行
客戶端
/Users/qinshen/go/bin ./hello-client
2019-08-03T22:48:21.939+0800 DEBUG ---------------- HTTP 請求 URL -------------
2019-08-03T22:48:21.940+0800 DEBUG 12345678 {"http request URL > ": "http://127.0.0.1:3001/uri/?id=1&where=%E4%B8%AD%E5%9B%BD%E6%B7%B1%E5%9C%B3&who=tsingson"}
2019-08-03T22:48:21.941+0800 DEBUG ---------------- HTTP 響應消耗時間-------------
2019-08-03T22:48:21.941+0800 DEBUG 12345678 {"elapsed": "939.037µs"}
2019-08-03T22:48:21.941+0800 DEBUG ---------------- HTTP 響應狀態碼 -------------
2019-08-03T22:48:21.941+0800 DEBUG 12345678 {"http status code": 200}
2019-08-03T22:48:21.941+0800 DEBUG ---------------- HTTP 響應 header 與 payload -------------
2019-08-03T22:48:21.941+0800 DEBUG ---------------- HTTP header 每一個鍵值對-------------
2019-08-03T22:48:21.941+0800 DEBUG 12345678 {"key": "Content-Length", "value": "15"}
2019-08-03T22:48:21.941+0800 DEBUG 12345678 {"key": "Content-Type", "value": "text/plain; charset=utf8"}
2019-08-03T22:48:21.941+0800 DEBUG 12345678 {"key": "Server", "value": "hello-world server"}
2019-08-03T22:48:21.941+0800 DEBUG 12345678 {"key": "Date", "value": "Sat, 03 Aug 2019 14:48:21 GMT"}
2019-08-03T22:48:21.941+0800 DEBUG 12345678 {"key": "Transactionid", "value": "12345678"}
2019-08-03T22:48:21.941+0800 DEBUG 12345678 {"key": "Connection", "value": "close"}
2019-08-03T22:48:21.941+0800 DEBUG ---------------- HTTP payload -------------
2019-08-03T22:48:21.941+0800 DEBUG 12345678 {"http payload": "Hello, tsingson"}
/Users/qinshen/go/bin
服務端
/Users/qinshen/go/bin ./hello-server
2019-08-03T22:48:12.234+0800 DEBUG ------------------ fasthttp 伺服器嘗試啟動------
2019-08-03T22:48:21.940+0800 DEBUG HTTP 訪問 TCP 連線 ID 1
2019-08-03T22:48:21.940+0800 DEBUG ---------------- HTTP URI -------------
2019-08-03T22:48:21.940+0800 DEBUG HTTP 請求 URL 原始資料 > {"request": "#0000000100000001 - 127.0.0.1:3001<->127.0.0.1:51927 - GET http://127.0.0.1:3001/uri/?id=1&where=%E4%B8%AD%E5%9B%BD%E6%B7%B1%E5%9C%B3&who=tsingson"}
2019-08-03T22:48:21.940+0800 DEBUG ---------------- HTTP URI 引數 -------------
2019-08-03T22:48:21.940+0800 DEBUG 在 URI 中的原始資料 > id=1&where=%E4%B8%AD%E5%9B%BD%E6%B7%B1%E5%9C%B3&who=tsingson
2019-08-03T22:48:21.940+0800 DEBUG ---------------- HTTP URI 每一個鍵值對 -------------
2019-08-03T22:48:21.940+0800 DEBUG 12345678 {"key": "id", "value": "1"}
2019-08-03T22:48:21.940+0800 DEBUG 12345678 {"key": "where", "value": "中國深圳"}
2019-08-03T22:48:21.940+0800 DEBUG 12345678 {"key": "who", "value": "tsingson"}
2019-08-03T22:48:21.940+0800 DEBUG ---------------- HTTP header 每一個鍵值對-------------
2019-08-03T22:48:21.940+0800 DEBUG 12345678 {"key": "Host", "value": "127.0.0.1:3001"}
2019-08-03T22:48:21.940+0800 DEBUG 12345678 {"key": "Content-Length", "value": "0"}
2019-08-03T22:48:21.940+0800 DEBUG 12345678 {"key": "Content-Type", "value": "text/plain; charset=utf8"}
2019-08-03T22:48:21.940+0800 DEBUG 12345678 {"key": "User-Agent", "value": "fasthttp-example web client"}
2019-08-03T22:48:21.941+0800 DEBUG 12345678 {"key": "Accept", "value": "text/plain; charset=utf8"}
2019-08-03T22:48:21.941+0800 DEBUG 12345678 {"key": "Transactionid", "value": "12345678"}
2019-08-03T22:48:21.941+0800 DEBUG ---------------- HTTP payload -------------
2019-08-03T22:48:21.941+0800 DEBUG 12345678 {"http payload": ""}
2019-08-03T22:48:21.941+0800 DEBUG ---------------- HTTP 響應 -------------
6. 小結
對比第 1 章節,第 5 章節 以 fasthttp 實現了一個 web 版本的 hello world:
- fasthttp web 客戶端處理請求: > * 構造了 URL, 並在 URL 中加上 key=value 鍵值對的資料 > * 設定 HTTP header, 注意 header 中的 TransactionID 欄位 > * 設定了 GET 請求方法 > * 多餘設定了 HTTP payload ------------ 注意, 伺服器把 GET 方法的 HTTP payload 丟掉了 > * ------ 發出請求
- fasthttp web 服務端處理響應: > * 設定了 HTTP status code > * 設定 HTTP header, 注意 header 中的 TransactionID 欄位 > * 設定 HTTP payload > * ------ 發出響應
- fasthttp 處理請求與響應, 遵從了 HTTP 規範, 非常相似
那麼, 看起了很繁瑣, 好吧, 後續文章我們再來談, 如何簡化, 以及如何得到高效能執行效率的同時, 能提高開發效率
_
_
_
7. 推薦
- 推薦謝大的小工具 https://github.com/astaxie/bat _
關於我
網名 tsingson (三明智, 江湖人稱 3 爺)
原 ustarcom IPTV/OTT 事業部播控產品線技術架構溼/解決方案工程溼角色 (8 年), 自由職業者,
喜歡音樂 (口琴,是第三/四/五屆廣東國際口琴嘉年華的主策劃人之一), 攝影與越野,
喜歡 golang 語言 (商用專案中主要用 postgres + golang )
_
_ tsingson 寫於中國深圳 小羅號口琴音樂中心, 2019/08/02
- 加微信實戰群請加微信(註明:實戰群):gocnio
相關文章
- Hello, World
- Hello World!
- Hello World
- Go - Hello WorldGo
- Docker Hello WorldDocker
- 【Java】Hello worldJava
- React Hello,WorldReact
- Mockito Hello WorldMockito
- ant Hello World
- Deep "Hello world!"
- Go:Hello WorldGo
- Hello World 程式的起源與歷史
- Hello Python worldPython
- react的”Hello World !“React
- WebGL 的 Hello WorldWeb
- ABAP程式Hello World
- dotnet hello world
- RabbitMQ tutorial - "Hello world!"MQ
- 輸出hello world
- java介紹、環境搭建與Hello,World!Java
- webpack+react環境搭建與hello worldWebReact
- Flutter Web 之 Hello WorldFlutterWeb
- ROS之初見Hello WorldROS
- 【Flutter 基礎】Hello WorldFlutter
- JMicro微服務Hello World微服務
- 01-C++ "hello world"C++
- RabbitMQ 入門 - Hello WorldMQ
- [WebAssembly 入門] Hello, world!Web
- 機器學習,Hello World from Javascript!機器學習JavaScript
- Spring版Hello WorldSpring
- Play框架之Hello, World!框架
- C# Hello,World(1)
- hello world"你知多少------300種程式語言中的"hello world"程式匯
- [系列] Go gRPC Hello WorldGoRPC
- PHPCPP安裝以及hello worldPHP
- spring boot(一)hello worldSpring Boot
- Smali 語法解析——Hello World
- C語言列印“Hello World“C語言