go語言Fasthttp實踐系列(1) Hello World

fasthttp 文章系列:git

  • fasthttp 概述 Hello World(本文)
  • fasthttp 以 Let's Encrypt! 支持HTTPS
  • fasthttp 客戶端與服務端的封裝, 日誌與路由
  • fasthttp 所謂 RESTful (兼介紹fastjson)
  • fasthttp 中間件( 簡單認證/ session會話...)
  • fasthttp 處理 JWT (及 JWT安全性)
  • fasthttp 對接非標準 web client (做爲AAA, 數據加解密)
  • fasthttp 緩存/proxy代理/反向代理
  • fasthttp 部署

[簡述] github.com/valyala/fas… 是 golang 中一個標誌性的高性能 HTTP庫, 主要用於 webserver 開發, 以及 web client / proxy 等. fasthttp 的高性能開發思路, 啓發了不少開發者.github

fasthttp 本身的介紹以下:golang

Fast HTTP package for Go. Tuned for high performance. Zero memory allocations in hot paths. Up to 10x faster than net/httpweb

Fast HTTP implementation for Go.docker

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.shell

事實上, 這有點小誇張, 但在必定場景下通過優化部署, 確是有很高的性能.json

近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 協議要很熟悉, 很熟悉.

推薦文章 淺談HTTP基本思路 -- 做者ESHLkangi

1.1 HTTP 1.x 協議簡述

簡單來講, HTTP 1.x 協議, 是一個被動式短鏈接的 client (請求 request ) - server ( 響應 response) 交互的規範:

  1. 協議通常來講, 以 TCP 通信協議爲基礎 ( 不談 QUIC 這個以 udp 爲底層實現的變異)

    web client 經過 DNS 把域名轉換成 IP 地址後, 與 web server 握手鍊接, 鏈接成功後, web client 客戶端向 web server 服務端發出請求, 服務端收到請求後, 向 client 客戶端應答

  2. 經過 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 請求並應答
    2. web 客戶端經過 http://192.168.1.1:8080 這個地址所指定的 web 服務器進行 tcp 鏈接, 鏈接成功後, web 客戶端向服務器發出 請求數據, web 服務端應答 響應數據
    3. 特別注意, 請求數據, 與響應數據, 聽從 HTTP 協議規定的統一格式
  3. 在 HTTP 1.x 協議中規定的傳輸( 請求/應答) 數據格式, 通常稱爲 HyperText, 是一種文本數據格式, 固然了, 在 TCP 傳輸時仍是二進制數據塊 ( 這是使用 fasthttp 的關鍵點) . 具體數據格式見 1.2 小節
  4. HTTP 協議規定了一些信令, 以下描述, 來區分不一樣的交互操做

    根據HTTP標準,HTTP請求可使用多種請求方法:

    • HTTP1.0定義了三種請求方法: GET, POST 和 HEAD方法。
    • HTTP1.1新增了五種請求方法:OPTIONS, PUT, DELETE, TRACE 和 CONNECT 方法。
  5. 因爲 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

 
複製代碼

1.3 http 1.x 協議小結與開發關聯點

這裏幾句很重要, 因此,

HTTP 1.x 幾個基礎點:

  1. HTTP 1.x 經過 tcp 進行通信
  2. 請求與響應的格式, 數據數據的格式是同樣的

    特別注意請求數據中的第一行,第二行 特別注意 HTTP header 與 HTTP payload 的那空行分隔

  3. 注意 URL/URI 中也包含有數據, 換個話說,在 http://192.168.1.1:3001/schedule?user_id=eq.2098735545843717147 中, 其餘部分 /schedule?user_id=eq.2098735545843717147 看作請求數據的一部分

從 HTTP 1.x 協議, 能夠總結 web 開發的要點

  1. 處理 tcp 通信, 包括:

    • 經過 dns 轉化域名獲得 IP 地址, 包括 ip4 / ip6 地址
    • 對 tcp 進行通信重寫或優化, 長鏈接或短鏈接, 都在這裏了
    • 或對 tcp 進行轉發 ( 這是 proxy ) 或劫持, 在 tcp 通信最底層進行一些特殊操做
  2. 對 URL /URI 進行處理, 這是路由尋址

    • 按 URI 及相關數據特徵進行攔截處理, 這是反向代理與緩存
    • 進行一些 URI 轉換, 例如 302 的重定向
    • 在 URI 中攜帶小部分數據的組裝與處理
  3. HTTP 數據處理

    • 對 HTTP header / HTTP payload 進行處理, 這是變化最多的部分, 按業務/功能的不一樣, 即簡單也複雜

fasthttp 的性能優化思路

  1. 重寫了在 tcp 之上進行 HTTP 握手/鏈接/通信的 goroutine pool 實現
  2. 對 http 數據基本按傳輸時的二進制進行延遲處理, 交由開發者按需決定
  3. 對二進制的數據進行了緩存池處理, 須要開發者手工處理以達到零內存分配

_

_


好, HTTP 1.x 就簡述到這了, 後面會大量引用到這一章節說的內容.


_

_

2. fasthttp 非"標準"的爭議, 及爲何選擇fasthttp

這一章節暫時不寫了, 須要一點時間進行整理文字

3. 開發環境及建立 project

我的主力用 MacOS 開發, 如下就以 MacOS 爲例

3.1. go 安裝, 環境變量及goproxy 配置

下載 golang 編譯器並安裝

下載地址爲 dl.google.com/go/go1.12.7…

任意下載到一個路徑下後, 雙擊安裝

或者, 打開一個 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 '

複製代碼

讓配置生效

source ~/.zshrc
複製代碼

或退出 terminal 並從新開啓一個新的 terminal

驗證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. 選用 uber-go/zap 日誌並簡單封裝

uber-go/zap 是一個高性能/穩定的 golag 日誌庫, 這裏簡單封裝一個小函數用於在 terminal 輸出相關日誌, 以 console 格式

導入 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 服務端響應求中的 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 服務端響應中的 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:

  1. fasthttp web 客戶端處理請求:
    • 構造了 URL, 並在 URL 中加上 key=value 鍵值對的數據
    • 設置 HTTP header, 注意 header 中的 TransactionID 字段
    • 設置了 GET 請求方法
    • 多餘設置了 HTTP payload ------------ 注意, 服務器把 GET 方法的 HTTP payload 丟掉了
    • ------ 發出請求
  2. fasthttp web 服務端處理響應:
    • 設置了 HTTP status code
    • 設置 HTTP header, 注意 header 中的 TransactionID 字段
    • 設置 HTTP payload
    • ------ 發出響應
  3. fasthttp 處理請求與響應, 聽從了 HTTP 規範, 很是類似

那麼, 看起了很繁瑣, 好吧, 後續文章咱們再來談, 如何簡化, 以及如何獲得高性能執行效率的同時, 能提升開發效率

_

_

_

關於我

網名 tsingson (三明智, 江湖人稱3爺)

原 ustarcom IPTV/OTT 事業部播控產品線技術架構溼/解決方案工程溼角色(8年), 自由職業者,

喜歡音樂(口琴,是第三/四/五屆廣東國際口琴嘉年華的主策劃人之一), 攝影與越野,

喜歡 golang 語言 (商用項目中主要用 postgres + golang )

_

_ tsingson 寫於中國深圳 小羅號口琴音樂中心, 2019/08/02

相關文章
相關標籤/搜索