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

相关文章
相关标签/搜索