文档章节

Go 开发 HTTP 的另一个选择 fasthttp

傅小黑
 傅小黑
发布于 2016/09/30 22:03
字数 1868
阅读 272
收藏 3
点赞 1
评论 0

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.Requesthttp.ResponseWriter; fasthttp 解析 HTTP 数据到 *fasthttp.RequestCtx,然后使用 sync.Pool 复用结构实例,减少对象的数量
  • fasthttp 会延迟解析 HTTP 请求中的数据,尤其是 Body 部分。这样节省了很多不直接操作 Body 的情况的消耗

但是因为 fasthttp 的实现与标准库差距较大,所以 API 的设计完全不同。使用时既需要理解 HTTP 的处理过程,又需要注意和标准库的差别。

package main

import (
	"fmt"

	"github.com/valyala/fasthttp"
)

// RequestHandler 类型,使用 RequestCtx 传递 HTTP 的数据
func httpHandle(ctx *fasthttp.RequestCtx) {
	fmt.Fprintf(ctx, "hello fasthttp") // *RequestCtx 实现了 io.Writer
}

func main() {
    // 一定要写 httpHandle,否则会有 nil pointer 的错误,没有处理 HTTP 数据的函数
	if err := fasthttp.ListenAndServe("0.0.0.0:12345", httpHandle); err != nil {
		fmt.Println("start fasthttp fail:", err.Error())
	}
}

<!--more-->

路由

net/http 提供 http.ServeMux 实现路由服务,但是匹配规则简陋,功能很简单,基本不会使用。fasthttp 吸取教训,默认没有提供路由支持。因此使用第三方的 fasthttp 的路由库 fasthttprouter 来辅助路由实现:

package main

import (
	"fmt"

	"github.com/buaazp/fasthttprouter"
	"github.com/valyala/fasthttp"
)

// fasthttprouter.Params 是路由匹配得到的参数,如规则 /hello/:name 中的 :name
func httpHandle(ctx *fasthttp.RequestCtx, _ fasthttprouter.Params) {
	fmt.Fprintf(ctx, "hello fasthttp")
}

func main() {
    // 使用 fasthttprouter 创建路由
	router := fasthttprouter.New()
	router.GET("/", httpHandle)
	if err := fasthttp.ListenAndServe("0.0.0.0:12345", router.Handler); err != nil {
		fmt.Println("start fasthttp fail:", err.Error())
	}
}

RequestCtx 操作

*RequestCtx 综合 http.Requesthttp.ResponseWriter 的操作,可以更方便的读取和返回数据。

首先,一个请求的基本数据是必然有的:

func httpHandle(ctx *fasthttp.RequestCtx) {
	ctx.SetContentType("text/html") // 记得添加 Content-Type:text/html,否则都当纯文本返回
	fmt.Fprintf(ctx, "Method:%s <br/>", ctx.Method())
	fmt.Fprintf(ctx, "URI:%s <br/>", ctx.URI())
	fmt.Fprintf(ctx, "RemoteAddr:%s <br/>", ctx.RemoteAddr())
	fmt.Fprintf(ctx, "UserAgent:%s <br/>", ctx.UserAgent())
	fmt.Fprintf(ctx, "Header.Accept:%s <br/>", ctx.Request.Header.Peek("Accept"))
}

fasthttp 还添加很多更方便的方法读取基本数据,如:

func httpHandle(ctx *fasthttp.RequestCtx) {
	ctx.SetContentType("text/html")
	fmt.Fprintf(ctx, "IP:%s <br/>", ctx.RemoteIP())
	fmt.Fprintf(ctx, "Host:%s <br/>", ctx.Host())
	fmt.Fprintf(ctx, "ConnectTime:%s <br/>", ctx.ConnTime()) // 连接收到处理的时间
	fmt.Fprintf(ctx, "IsGET:%v <br/>", ctx.IsGet())          // 类似有 IsPOST, IsPUT 等
}

更详细的 API 可以阅读 godoc.org

表单数据

RequestCtx 有同标准库的 FormValue() 方法,还对 GET 和 POST/PUT 传递的参数进行了区分:

func httpHandle(ctx *fasthttp.RequestCtx) {
	ctx.SetContentType("text/html")

	// GET ?abc=abc&abc=123
	getValues := ctx.QueryArgs()
	fmt.Fprintf(ctx, "GET abc=%s <br/>",
		getValues.Peek("abc")) // Peek 只获取第一个值
	fmt.Fprintf(ctx, "GET abc=%s <br/>",
		bytes.Join(getValues.PeekMulti("abc"), []byte(","))) // PeekMulti 获取所有值

	// POST xyz=xyz&xyz=123
	postValues := ctx.PostArgs()
	fmt.Fprintf(ctx, "POST xyz=%s <br/>",
		postValues.Peek("xyz"))
	fmt.Fprintf(ctx, "POST xyz=%s <br/>",
		bytes.Join(postValues.PeekMulti("xyz"), []byte(",")))
}

可以看到输出结果:

GET abc=abc 
GET abc=abc,123 
POST xyz=xyz 
POST xyz=xyz,123 
Body 消息体

fasthttp 提供比标准库丰富的 Body 操作 API,而且支持解析 Gzip 过的数据:

func httpHandle(ctx *fasthttp.RequestCtx) {
	body := ctx.PostBody() // 获取到的是 []byte
	fmt.Fprintf(ctx, "Body:%s", body)

	// 因为是 []byte,解析 JSON 很简单
	var v interface{}
	json.Unmarshal(body,&v)
}

func httpHandle2(ctx *fasthttp.RequestCtx) {
	ungzipBody, err := ctx.Request.BodyGunzip()
	if err != nil {
		ctx.SetStatusCode(fasthttp.StatusServiceUnavailable)
		return
	}
	fmt.Fprintf(ctx, "Ungzip Body:%s", ungzipBody)
}

上传文件

fasthttp 对文件上传的部分没有做大修改,使用和 net/http 一样:

func httpHandle(ctx *fasthttp.RequestCtx) {
	// 这里直接获取到 multipart.FileHeader, 需要手动打开文件句柄
	f, err := ctx.FormFile("file")
	if err != nil {
		ctx.SetStatusCode(500)
		fmt.Println("get upload file error:", err)
		return
	}
	fh, err := f.Open()
	if err != nil {
		fmt.Println("open upload file error:", err)
		ctx.SetStatusCode(500)
		return
	}
	defer fh.Close() // 记得要关

	// 打开保存文件句柄
	fp, err := os.OpenFile("saveto.txt", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
	if err != nil {
		fmt.Println("open saving file error:", err)
		ctx.SetStatusCode(500)
		return
	}
	defer fp.Close() // 记得要关

	if _, err = io.Copy(fp, fh); err != nil {
		fmt.Println("save upload file error:", err)
		ctx.SetStatusCode(500)
		return
	}
	ctx.Write([]byte("save file successfully!"))
}

上面的操作可以对比我写的上一篇文章 Go 开发 HTTP,非常类似。多文件上传同样使用 *RequestCtx.MultipartForm() 获取到整个表单内容,各个文件处理就可以。

返回内容

不像 http.ResponseWriter 那么简单,*RequestCtx*RequestCtx.Response 提供了丰富的 API 为 HTTP 返回数据:

func httpHandle(ctx *fasthttp.RequestCtx) {
	ctx.WriteString("hello,fasthttp")
	// 因为实现不同,fasthttp 的返回内容不是即刻返回的
	// 不同于标准库,添加返回内容后设置状态码,也是有效的
	ctx.SetStatusCode(404)

	// 返回的内容也是可以获取的,不需要标准库的用法,需要自己扩展 http.ResponseWriter
	fmt.Println(string(ctx.Response.Body()))
}

下载文件也有直接的方法:

func httpHandle(ctx *fasthttp.RequestCtx) {
	ctx.SendFile("abc.txt")
}

可以阅读 fasthttp.ResponseAPI 文档,有很多方法可以简化操作。

RequestCtx 复用引发数据竞争

RequestCtxfasthttp 中使用 sync.Pool 复用。在执行完了 RequestHandler 后当前使用的 RequestCtx 就返回池中等下次使用。如果你的业务逻辑有跨 goroutine 使用 RequestCtx,那可能遇到:同一个 RequestCtxRequestHandler 结束时放回池中,立刻被另一次连接使用;业务 goroutine 还在使用这个 RequestCtx,读取的数据发生变化。

为了解决这种情况,一种方式是给这次请求处理设置 timeout ,保证 RequestCtx 的使用时 RequestHandler 没有结束:

func httpHandle(ctx *fasthttp.RequestCtx) {
	resCh := make(chan string, 1)
	go func() {
		// 这里使用 ctx 参与到耗时的逻辑中
		time.Sleep(5 * time.Second)
		resCh <- string(ctx.FormValue("abc"))
	}()

	// RequestHandler 阻塞,等着 ctx 用完或者超时
	select {
	case <-time.After(1 * time.Second):
		ctx.TimeoutError("timeout")
	case r := <-resCh:
		ctx.WriteString("get: abc = " + r)
	}
}

还提供 fasthttp.TimeoutHandler 帮助封装这类操作。

另一个角度,fasthttp 不推荐复制 RequestCtx。但是根据业务思考,如果只是收到请求数据立即返回,后续处理数据的情况,复制 RequestCtx.Request 是可以的,因此也可以使用:

func httpHandle(ctx *fasthttp.RequestCtx) {
	var req fasthttp.Request
	ctx.Request.CopyTo(&req)
	go func() {
		time.Sleep(5 * time.Second)
		fmt.Println("GET abc=" + string(req.URI().QueryArgs().Peek("abc")))
	}()
	ctx.WriteString("hello fasthttp")
}

需要注意 RequestCtx.Response 也是可以 Response.CopyTo 复制的。但是如果 RequestHandler 结束,RequestCtx.Response 肯定已发出返回内容。在别的 goroutine 修改复制的 Response,没有作用的。

BytesBuffer

fasthttp 用了很多特殊的优化技巧来提高性能。一些方法也暴露出来可以使用,比如重用的 Bytes:

func httpHandle(ctx *fasthttp.RequestCtx) {
	b := fasthttp.AcquireByteBuffer()
	b.B = append(b.B, "Hello "...)
	// 这里是编码过的 HTML 文本了,&gt;strong 等
	b.B = fasthttp.AppendHTMLEscape(b.B, "<strong>World</strong>")
	defer fasthttp.ReleaseByteBuffer(b) // 记得释放

	ctx.Write(b.B)
}

原理就是简单的把 []byte 作为复用的内容在池中存取。对于非常频繁存取 BytesBuffer 的情况,可能同一个 []byte 不停地被使用 append,而频繁存取导致没有空闲时刻,[]byte 无法得到释放,使用时需要注意一点。

fasthttp 的不足

两个比较大的不足:

  • HTTP/2.0 不支持
  • WebSocket 不支持

严格来说 Websocket 通过 Hijack() 是可以支持的,但是 fasthttp 想自己提供直接操作的 API。那还需要等待开发。

总结

比较标准库的粗犷,fasthttp 有更精细的设计,对 Go 网络并发编程的主要痛点做了很多工作,达到了很好的效果。目前,irisecho 支持 fasthttp,性能上和使用 net/http 的别的 Web 框架对比有明显的优势。如果选择 Web 框架,支持 fasthttp 可以看作是一个真好的卖点,值得注意。

© 著作权归作者所有

共有 人打赏支持
傅小黑
粉丝 154
博文 7
码字总数 13850
作品 1
厦门
高级程序员
Go 的快速 HTTP 包--fasthttp

fasthttp 是 Go 的快速 HTTP 实现,当前在 1M 并发的生产环境使用非常成功,可以从单个服务器进行 100K qps 的持续连接。 HTTP 服务器性能与 net/http 比较 总而言之,fasthttp 比 net/http...

叶秀兰
2015/11/26
2.4K
0
使用 fasthttp 时要注意的两个点

我们做的是聚合支付系统,使用的是fasthttp 作为http server, http client 也是使用fasthttp 1. 第一个问题出现的场景是我们使用fasthttp client 请求微信支付时报了这个err ErrConnectionCl...

fireblue火蓝
2017/12/16
0
0
PhalGo-Echo路由

PhalGo-Echo路由 Echo官网地址:https://labstack.com/echo Echo是PhalGo最核心的组件,负责了整体的请求路由返回等功能,并且Echo支持HTTP2协议以及HTTPS协议 为什么选择Echo 在初期笔者考虑过...

喵了_个咪
2016/05/24
437
0
前端遇上Go: 静态资源增量更新的新实践

为什么要做增量更新 美团金融的业务在过去的一段时间里发展非常快速。在业务增长的同时,我们也注意到,很多用户的支付环境,其实是在弱网环境中的。 大家知道,前端能够服务用户的前提是 Ja...

美团技术团队
07/06
0
0
Golang byte buffers tricks

Tricks with buffers The following tricks are used by fasthttp. Use them in your code too. Standard Go functions accept nil buffers var ( )dst = append(dst, src...) // is legal i......

shengjuntu
2016/11/15
7
0
go语言学习--pongo2 fasthttp fasthttprouter pgx

一、安装 OS:Windows 10 X64 go:go1.8.3.windows-amd64.msi 二、安装golang包 pongo2,fasthttp,fasthttprouter,pgx 1.建立项目目录 f:/go_prog 2.安装相关包 Microsoft Windows [版本 10.0......

pgmia
2017/08/18
0
0
HTTP接口定义与请求参数绑定中间件--Apiware

Apiware 将 Go 语言 net/http 及 fasthttp 请求的指定参数绑定到结构体,并验证参数值的合法性。 建议您可以使用结构体作为 web 框架的 Handler,并用该中间件快速绑定请求参数,节省了大量参...

henrylee2cn
2016/11/03
207
0
Golang资料集

该资源的github地址:Qix 《Platform-native GUI library for Go》 介绍:跨平台的golang GUI库,支持Windows(xp以上),Unix,Mac OS X(Mac OS X 10.7以上) 《Gopm 快速入门》 介绍:Gopm(Go 包管...

ty4z2008
2016/03/11
0
0
Go web开发框架--Baa

Baa 一个简单高效的Go web开发框架。主要有路由、中间件,依赖注入和HTTP上下文构成。 Baa 不使用 反射和正则,没有魔法的实现。 快速上手 安装: go get -u gopkg.in/baa.v1 示例: packag...

Coldstar
2016/04/18
634
0
Go 语言编写的 Web 框架--Gem Framework

Gem 补充:不兼容Windows Gem 是一个用 Go(golang)语言编写的简单而又快速的 Web 框架,用于构建 REST API 或 web 应用,基于 fasthttp。 安装 go get github.com/go-gem/gem 特性 Gracef...

匿名
2016/12/02
5.1K
6

没有更多内容

加载失败,请刷新页面

加载更多

下一页

【面试题】盲人坐飞机

有100位乘客乘坐飞机,其中有一位是盲人,每位乘客都按自己的座位号就坐。由于盲人看不见自己的座位号,所以他可能会坐错位置,而自己的座位被占的乘客会随便找个座位就坐。问所有乘客都坐对...

garkey
今天
0
0
谈谈神秘的ES6——(二)ES6的变量

谈谈神秘的ES6——(二)ES6的变量 我们在《零基础入门JavaScript》的时候就说过,在ES5里,变量是有弊端的,我们先来回顾一下。 首先,在ES5中,我们所有的变量都是通过关键字var来定义的。...

JandenMa
今天
1
0
arts-week1

Algorithm 594. Longest Harmonious Subsequence - LeetCode 274. H-Index - LeetCode 219. Contains Duplicate II - LeetCode 217. Contains Duplicate - LeetCode 438. Find All Anagrams ......

yysue
今天
0
0
NNS拍卖合约

前言 关于NNS的介绍,这里就不多做描述,相关的信息可以查看NNS的白皮书http://doc.neons.name/zh_CN/latest/nns_background.html。 首先nns中使用的竞价货币是sgas,关于sgas介绍可以戳htt...

红烧飞鱼
今天
1
0
Java IO类库之管道流PipeInputStream与PipeOutputStream

一、java管道流介绍 在java多线程通信中管道通信是一种重要的通信方式,在java中我们通过配套使用管道输出流PipedOutputStream和管道输入流PipedInputStream完成线程间通信。多线程管道通信的...

老韭菜
今天
0
0
用Python绘制红楼梦词云图,竟然发现了这个!

Python在数据分析中越来越受欢迎,已经达到了统计学家对R的喜爱程度,Python的拥护者们当然不会落后于R,开发了一个个好玩的数据分析工具,下面我们来看看如何使用Python,来读红楼梦,绘制小...

猫咪编程
今天
1
0
Java中 发出请求获取别人的数据(阿里云 查询IP归属地)

1.效果 调用阿里云的接口 去定位IP地址 2. 代码 /** * 1. Java中远程调用方法 * http://localhost:8080/mavenssm20180519/invokingUrl.action * @Title: invokingUrl * @Description: * @ret......

Lucky_Me
今天
1
0
protobuf学习笔记

相关文档 Protocol buffers(protobuf)入门简介及性能分析 Protobuf学习 - 入门

OSC_fly
昨天
0
0
Mybaties入门介绍

Mybaties和Hibernate是我们在Java开发中应用的比较多的两个ORM框架。当然,目前Mybaties正在慢慢取代Hibernate,这是因为相比较Hibernate而言Mybaties性能更好,响应更快,更加灵活。我们在开...

王子城
昨天
2
0
编程学习笔记之python深入之装饰器案例及说明文档[图]

编程学习笔记之python深入之装饰器案例及说明文档[图] 装饰器即在不对一个函数体进行任何修改,以及不改变整体的原本意思的情况下,增加函数功能的新函数,因为这个新函数对旧函数进行了装饰...

原创小博客
昨天
1
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部