Hertz自学自讲(三)

目录

1、Engine

初始化服务

Default

New

服务运行与退出

Spin

补充:优雅退出

Run

SetCustomSignalWaiter

Use

补充:中间件 

补充:Handler

Handler 相关操作

流式处理

流式读

流式写

钩子函数

Panic 处理函数

获取路由信息

Routes

2、路由

路由组

路由类型

命名参数路由

通配参数路由

获取路由注册信息

NoRoute 与 NoMethod 使用

重定向尾斜杠

3、客户端

Client 配置

Client Request 配置

发送请求

Do

DoRedirects

Get

Post

请求超时

WithRequestTimeout

DoTimeout

DoDeadline

请求重试

SetRetryIfFunc

添加请求内容

上传文件

流式读响应内容 

服务发现

TLS

正向代理

SetProxy

关闭空闲连接

CloseIdleConnections

中间件

Use

UseAsLast

4、网络库

使用方式

网络库选择


1、Engine

Engine | CloudWeGo

server.Hertz 是 Hertz 的核心类型,它由 route.Engine 以及 signalWaiter 组成,Hertz 服务器的启动、路由注册、中间件注册以及退出等重要方法均包含在 server.Hertz 中。以下是 server.Hertz 的定义:

type Hertz struct {
    *route.Engine 
    // 用于接收信号以实现优雅退出 
    signalWaiter func (err chan error) error
}

route.Engine 为 server.Hertz 的重要组成部分,Engine 的定义位于 Engine

配置项默认值说明
WithTransportnetwork.NewTransporter更换底层 transport
WithHostPorts:8888指定监听的地址和端口
WithKeepAliveTimeout1mintcp 长连接保活时间,一般情况下不用修改,更应该关注 idleTimeout
WithReadTimeout3min底层读取数据超时时间
WithIdleTimeout3min长连接请求链接空闲超时时间
WithMaxRequestBodySize4 * 1024 * 1024配置最大的请求体大小
WithRedirectTrailingSlashtrue自动根据末尾的 / 转发,例如:如果 router 只有 /foo/,那么 /foo 会重定向到 /foo/ ;如果只有 /foo,那么 /foo/ 会重定向到 /foo
WithRemoveExtraSlashfalseRemoveExtraSlash 当有额外的 / 时也可以当作参数。如:user/:name,如果开启该选项 user//xiaoming 也可匹配上参数
WithUnescapePathValuestrue如果开启,请求路径会被自动转义(eg. ‘%2F’ -> ‘/')。如果 UseRawPath 为 false(默认情况),则 UnescapePathValues 实际上为 true,因为 .URI().Path() 将被使用,它已经是转义后的。设置该参数为 false,需要配合 WithUseRawPath(true)
WithUseRawPathfalse如果开启,会使用原始 path 进行路由匹配
WithHandleMethodNotAllowedfalse如果开启,当当前路径不能被匹配上时,server 会去检查其他方法是否注册了当前路径的路由,如果存在则会响应"Method Not Allowed",并返回状态码 405; 如果没有,则会用 NotFound 的 handler 进行处理
WithDisablePreParseMultipartFormfalse如果开启,则不会预处理 multipart form。可以通过 ctx.Request.Body() 获取到 body 后由用户处理
WithStreamBodyfalse如果开启,则会使用流式处理 body
WithNetwork“tcp”设置网络协议,可选:tcp,udp,unix(unix domain socket),默认为 tcp
WithExitWaitTime5s设置优雅退出时间。Server 会停止建立新的连接,并对关闭后的每一个请求设置 Connection: Close 的 header,当到达设定的时间关闭 Server。当所有连接已经关闭时,Server 可以提前关闭
WithTLSnil配置 server tls 能力,详情可见 TLS
WithListenConfignil设置监听器配置,可用于设置是否允许 reuse port 等
WithALPNfalse是否开启 ALPN
WithTracer[]interface{}{}注入 tracer 实现,如不注入 Tracer 实现,默认关闭
WithTraceLevelLevelDetailed设置 trace level
WithWriteTimeout无限长写入数据超时时间
WithRedirectFixedPathfalse如果开启,当当前请求路径不能匹配上时,server 会尝试修复请求路径并重新进行匹配,如果成功匹配并且为 GET 请求则会返回状态码 301 进行重定向,其他请求方式返回 308 进行重定向
WithBasePath/设置基本路径,前缀和后缀必须为 /
WithMaxKeepBodySize4 * 1024 * 1024设置回收时保留的请求体和响应体的最大大小。单位:字节
WithGetOnlyfalse如果开启则只接受 GET 请求
WithKeepAlivetrue如果开启则使用 HTTP 长连接
WithAltTransportnetwork.NewTransporter设置备用 transport
WithH2Cfalse设置是否开启 H2C
WithReadBufferSize4 * 1024设置读缓冲区大小,同时限制 HTTP header 大小
WithRegistryregistry.NoopRegistry, nil设置注册中心配置,服务注册信息
WithAutoReloadRenderfalse, 0设置自动重载渲染配置
WithDisablePrintRoutefalse设置是否禁用 debugPrintRoute
WithOnAcceptnil设置在 netpoll 中当一个连接被接受但不能接收数据时的回调函数,在 go net 中在转换 TLS 连接之前被调用
WithOnConnectnil设置 onConnect 函数。它可以接收来自 netpoll 连接的数据。在 go net 中,它将在转换 TLS 连接后被调用

这个是Hertz框架的“引擎”,是Hertz框架的入口。引擎中则包含有所有的方法,如果想要使用这个框架就必须依靠这个引擎。 

简单来说,就是我们通过Engine来开启和关闭网络服务。

那我们来看看怎么初始化吧!

初始化服务

func Default(opts ...config.Option) *Hertz
func New(opts ...config.Option) *Hertz

Default

Default 用于初始化服务,默认使用了 Recovery 中间件以保证服务在运行时不会因为 panic 导致服务崩溃。

函数签名:

func Default(opts ...config.Option) *Hertz

示例代码:

func main() {
    h := server.Default()
    h.Spin()
}

New

New 用于初始化服务,没有使用默认的 Recovery 中间件。

函数签名:

func New(opts ...config.Option) *Hertz

示例代码:

func main() {
    h := server.New()
    h.Spin()
}

 平常我们的开发中一般会使用 Defalt 而不会直接使用 New ,因为 Defalt 会使用默认中间件 Recovery。 

服务运行与退出

func (h *Hertz) Spin()
func (engine *Engine) Run() (err error)
func (h *Hertz) SetCustomSignalWaiter(f func(err chan error) error)

Spin

Spin 函数用于运行 Hertz 服务器,接收到退出信号后可退出服务。

该函数支持服务的优雅退出,优雅退出的详细内容请看 优雅退出

在使用 服务注册发现 的功能时,Spin 会在服务启动时将服务注册进入注册中心,并使用 signalWaiter 监测服务异常。

函数签名:

func (h *Hertz) Spin()

示例代码:

func main() {
    h := server.Default()
    h.Spin()
}

补充:优雅退出

 

Run

Run 函数用于运行 Hertz 服务器,接收到退出信号后可退出服务。

该函数不支持服务的优雅退出,除非有特殊需求,不然一般使用 Spin 函数用于运行服务。

函数签名:

func (engine *Engine) Run() (err error)

示例代码:

func main() {
    h := server.Default()
    if err := h.Run(); err != nil {
        // ...
    	panic(err)
    }
}

SetCustomSignalWaiter

SetCustomSignalWaiter 函数用于自定义服务器接收信号后的处理函数,若没有设置自定义函数,Hertz 使用 waitSignal 函数作为信号处理的默认实现方式,详细内容请看优雅退出

函数签名:

func (h *Hertz) SetCustomSignalWaiter(f func(err chan error) error)

示例代码:

func main() {
	h := server.New()
	h.SetCustomSignalWaiter(func(err chan error) error {
		return nil
	})
	h.Spin()
}

 

Use

Use 函数用于将中间件注册进入路由

Hertz 支持用户自定义中间件,Hertz 已经实现了一些常用的中间件,详情见 hertz-contrib

Hertz 支持的中间件的使用方法包括全局注册路由组级别和单一路由级别的注册,详情见 服务端中间件

Use 函数中 middleware 的形参必须为 app.HandlerFunc 的 http 处理函数:

type HandlerFunc func (ctx context.Context, c *app.RequestContext)

函数签名:

func (engine *Engine) Use(middleware ...app.HandlerFunc) IRoutes

示例代码:

func main() {
    h := server.New()
    // 将内置的 Recovery 中间件注册进入路由
    h.Use(recovery.Recovery())
    // 使用自定义的中间件
    h.Use(exampleMiddleware())
}

func exampleMiddleware() app.handlerFunc {
    return func(ctx context.Context, c *app.RequestContext) {
        // 在 Next 中的函数执行之前打印日志
        hlog.Info("print before...")
        // 使用 Next 使得路由匹配的函数执行
        c.Next(ctx)
        // 在 Next 中的函数执行之后打印日志
        hlog.Ingo("print after...")
    }
}

补充:中间件 

Hertz中间件的种类是多种多样的,简单分为两大类:

  • 服务端中间件
  • 客户端中间件

服务器中间件:

 

中间件可以在请求更深入地传递到业务逻辑之前或之后执行:

  • 中间件可以在请求到达业务逻辑之前执行,比如执行身份认证和权限认证,当中间件只有初始化(pre-handle)相关逻辑,且没有和 real handler 在一个函数调用栈中的需求时,中间件中可以省略掉最后的.Next,如图1的中间件 B。
  • 中间件也可以在执行过业务逻辑之后执行,比如记录响应时间和从异常中恢复。如果在业务 handler 处理之后有其它处理逻辑( post-handle ),或对函数调用链(栈)有强需求,则必须显式调用.Next,如图1的中间件 C。

使用默认中间件

Hertz 框架已经预置了常用的 recover 中间件,使用 server.Default() 默认可以注册该中间件。

客户端中间件:

补充:Handler

Handler 相关操作

一个服务端中间件即为一个 Handler,Handler 相关操作见 Handler

小demo:

package main

import (
	"context"
	"github.com/cloudwego/hertz/pkg/app"
	"github.com/cloudwego/hertz/pkg/app/server"
	"github.com/cloudwego/hertz/pkg/protocol/consts"
)

func main() {

	h := server.Default()

	h.GET("/hello", func(ctx context.Context, c *app.RequestContext) {
		c.String(consts.StatusOK, "Hello hertz!")
	})
	h.Spin()
}

 

流式处理

Hertz 支持 Server 的流式处理,包括流式读和流式写。

注意:由于 netpoll 和 go net 触发模式不同,netpoll 流式为 “伪” 流式(由于 LT 触发,会由网络库将数据读取到网络库的 buffer 中),在大包的场景下(如:上传文件等)可能会有内存问题,推荐使用 go net。

流式读

Hertz Server 支持流式读取请求内容。

func main() {
	h := server.Default(server.WithHostPorts("127.0.0.1:8080"), server.WithStreamBody(true), server.WithTransport(standard.NewTransporter))

	h.POST("/bodyStream", handler)

	h.Spin()
}

func handler(ctx context.Context, c *app.RequestContext) {
	// Acquire body streaming
	bodyStream := c.RequestBodyStream()
	// Read half of body bytes
	p := make([]byte, c.Request.Header.ContentLength()/2)
	r, err := bodyStream.Read(p)
	if err != nil {
		panic(err)
	}
	left, _ := ioutil.ReadAll(bodyStream)
	c.String(consts.StatusOK, "bytes streaming_read: %d\nbytes left: %d\n", r, len(left))
}

流式写

Hertz Server 支持流式写入响应。

提供了两种方式:

用户在 handler 中通过 ctx.SetBodyStream 函数传入一个 io.Reader,然后按与示例代码(利用 channel 控制数据分块及读写顺序)类似的方式分块读写数据。注意,数据需异步写入。

若用户事先知道传输数据的总长度,可以在 ctx.SetBodyStream 函数中传入该长度进行流式写,示例代码如 /streamWrite1

若用户事先不知道传输数据的总长度,可以在 ctx.SetBodyStream 函数中传入 -1 以 Transfer-Encoding: chunked 的方式进行流式写,示例代码如 /streamWrite2

示例代码:

func main() {
    h := server.Default(server.WithHostPorts("127.0.0.1:8080"), server.WithStreamBody(true), server.WithTransport(standard.NewTransporter))

    h.GET("/streamWrite1", func(c context.Context, ctx *app.RequestContext) {
        rw := newChunkReader()
        line := []byte("line\r\n")
        ctx.SetBodyStream(rw, 500*len(line))

        go func() {
            for i := 1; i <= 500; i++ {
                // For each streaming_write, the upload_file prints
                rw.Write(line)
                fmt.Println(i)
                time.Sleep(10 * time.Millisecond)
            }
            rw.Close()
        }()

        go func() {
            <-ctx.Finished()
            fmt.Println("request process end")
        }()
    })

    h.GET("/streamWrite2", func(c context.Context, ctx *app.RequestContext) {
        rw := newChunkReader()
        // Content-Length may be negative:
        // -1 means Transfer-Encoding: chunked.
        ctx.SetBodyStream(rw, -1)

        go func() {
            for i := 1; i < 1000; i++ {
                // For each streaming_write, the upload_file prints
                rw.Write([]byte(fmt.Sprintf("===%d===\n", i)))
                fmt.Println(i)
                time.Sleep(100 * time.Millisecond)
            }
            rw.Close()
        }()

        go func() {
            <-ctx.Finished()
            fmt.Println("request process end")
        }()
    })

    h.Spin()
}

type ChunkReader struct {
    rw  bytes.Buffer
    w2r chan struct{}
    r2w chan struct{}
}

func newChunkReader() *ChunkReader {
    var rw bytes.Buffer
    w2r := make(chan struct{})
    r2w := make(chan struct{})
    cr := &ChunkReader{rw, w2r, r2w}
    return cr
}

var closeOnce = new(sync.Once)

func (cr *ChunkReader) Read(p []byte) (n int, err error) {
    for {
        _, ok := <-cr.w2r
        if !ok {
            closeOnce.Do(func() {
                close(cr.r2w)
            })
            n, err = cr.rw.Read(p)
            return
        }

        n, err = cr.rw.Read(p)

        cr.r2w <- struct{}{}

        if n == 0 {
            continue
        }
        return
    }
}

func (cr *ChunkReader) Write(p []byte) (n int, err error) {
    n, err = cr.rw.Write(p)
    cr.w2r <- struct{}{}
    <-cr.r2w
    return
}

func (cr *ChunkReader) Close() {
    close(cr.w2r)
}

用户可以在 handler 中使用 pkg/protocol/http1/resp/writer 下提供的 NewChunkedBodyWriter 方法劫持 response 的 writer,然后使用 ctx.Write 函数将分块数据写入 Body 并将分块数据使用 ctx.Flush 函数立即发送给客户端。

示例代码:

h.GET("/flush/chunk", func(c context.Context, ctx *app.RequestContext) {
	// Hijack the writer of response
	ctx.Response.HijackWriter(resp.NewChunkedBodyWriter(&ctx.Response, ctx.GetWriter()))

	for i := 0; i < 10; i++ {
        ctx.Write([]byte(fmt.Sprintf("chunk %d: %s", i, strings.Repeat("hi~", i)))) // nolint: errcheck
        ctx.Flush()                                                                 // nolint: errcheck
        time.Sleep(200 * time.Millisecond)
	}
})

这两种方式的区别:第一种在执行完 handler 逻辑后再将数据按分块发送给客户端,第二种在 handler 逻辑中就可以将分块数据发送出去。

钩子函数

钩子函数(Hooks)是一个通用的概念,表示某事件触发时所伴随的操作。

Hertz 提供了全局的 Hook 注入能力,用于在服务触发启动后和退出前注入自己的处理逻辑,详细信息可见 Hooks

Panic 处理函数

用于设置当程序发生 panic 时的处理函数,默认为 nil

注意: 如果同时设置了 PanicHandler 和 Recovery 中间件,则 Recovery 中间件会覆盖 PanicHandler 的处理逻辑。

示例代码:

func main() {
    h := server.New()
    // 在 panic 时,会触发 PanicHandler 中的函数,返回 500 状态码并携带错误信息
    h.PanicHandler = func(c context.Context, ctx *app.RequestContext) {
        ctx.JSON(500, utils.H{
            "message": "panic",
        })
    }
    h.GET("/hello", func(c context.Context, ctx *app.RequestContext) {
        panic("panic")
    })
    h.Spin()
}

获取路由信息

func (engine *Engine) Routes() (routes RoutesInfo)

Routes

Routes 函数返回一个按 HTTP 方法划分的包含路由信息(HTTP 方法名,路由路径,请求处理函数名)的切片。

函数签名:

func (engine *Engine) Routes() (routes RoutesInfo)

示例代码:

func getHandler() app.HandlerFunc {
	return func(c context.Context, ctx *app.RequestContext) {
		ctx.String(consts.StatusOK, "get handler")
	}
}

func postHandler() app.HandlerFunc {
	return func(c context.Context, ctx *app.RequestContext) {
		ctx.String(consts.StatusOK, "post handler")
	}
}

func main() {
	h := server.Default()
	h.GET("/get", getHandler())
	h.POST("/post", postHandler())
	routesInfo := h.Routes()
	fmt.Printf("%v\n", routesInfo)
	// [{GET /get main.getHandler.func1 0xb2afa0} {POST /post main.postHandler.func1 0xb2b060}]
}

2、路由

路由 | CloudWeGo

Hertz 提供的路由功能。

 

Hertz 提供了 GETPOSTPUTDELETEANY 等方法用于注册路由。

方法介绍
Hertz.GET用于注册 HTTP Method 为 GET 的方法
Hertz.POST用于注册 HTTP Method 为 POST 的方法
Hertz.DELETE用于注册 HTTP Method 为 DELETE 的方法
Hertz.PUT用于注册 HTTP Method 为 PUT 的方法
Hertz.PATCH用于注册 HTTP Method 为 PATCH 的方法
Hertz.HEAD用于注册 HTTP Method 为 HEAD 的方法
Hertz.OPTIONS用于注册 HTTP Method 为 OPTIONS 的方法
Hertz.Handle这个方法支持用户手动传入 HTTP Method 用来注册方法,当用于注册普通的 HTTP Method 方法时和上述的方法作用是一致的,并且这个方法同时也支持用于注册自定义 HTTP Method 方法
Hertz.Any用于注册所有 HTTP Method 方法
package main

import (
	"context"
	"github.com/cloudwego/hertz/pkg/app"
	"github.com/cloudwego/hertz/pkg/app/server"
	"github.com/cloudwego/hertz/pkg/protocol/consts"
)

func main(){
	h := server.Default(server.WithHostPorts("127.0.0.1:8080"))

	h.StaticFS("/", &app.FS{Root: "./", GenerateIndexPages: true})

	h.GET("/get", func(ctx context.Context, c *app.RequestContext) {
		c.String(consts.StatusOK, "get")
	})
	h.POST("/post", func(ctx context.Context, c *app.RequestContext) {
		c.String(consts.StatusOK, "post")
	})
	h.PUT("/put", func(ctx context.Context, c *app.RequestContext) {
		c.String(consts.StatusOK, "put")
	})
	h.DELETE("/delete", func(ctx context.Context, c *app.RequestContext) {
		c.String(consts.StatusOK, "delete")
	})
	h.PATCH("/patch", func(ctx context.Context, c *app.RequestContext) {
		c.String(consts.StatusOK, "patch")
	})
	h.HEAD("/head", func(ctx context.Context, c *app.RequestContext) {
		c.String(consts.StatusOK, "head")
	})
	h.OPTIONS("/options", func(ctx context.Context, c *app.RequestContext) {
		c.String(consts.StatusOK, "options")
	})
	h.Any("/ping_any", func(ctx context.Context, c *app.RequestContext) {
		c.String(consts.StatusOK, "any")
	})
	h.Handle("LOAD","/load", func(ctx context.Context, c *app.RequestContext) {
		c.String(consts.StatusOK, "load")
	})
	h.Spin()
}

以上大概就是路由的各种用法。

我们还可以给路由分组。

路由组

Hertz 提供了路由组 ( Group ) 的能力,用于支持路由分组的功能,同时中间件也可以注册到路由组上。

示例代码:

package main

import (
	"context"
	"github.com/cloudwego/hertz/pkg/app"
	"github.com/cloudwego/hertz/pkg/app/server"
	"github.com/cloudwego/hertz/pkg/protocol/consts"
)

func main(){
	h := server.Default(server.WithHostPorts("127.0.0.1:1111"))
	v1 := h.Group("/v1")
	v1.GET("/get", func(ctx context.Context, c *app.RequestContext) {
		c.String(consts.StatusOK, "get")
	})
	v1.POST("/post", func(ctx context.Context, c *app.RequestContext) {
		c.String(consts.StatusOK, "post")
	})
	v2 := h.Group("/v2")
	v2.PUT("/put", func(ctx context.Context, c *app.RequestContext) {
		c.String(consts.StatusOK, "put")
	})
	v2.DELETE("/delete", func(ctx context.Context, c *app.RequestContext) {
		c.String(consts.StatusOK, "delete")
	})
	h.Spin()
}

在路由组中使用中间件

如下示例在路由组中使用 BasicAuth 中间件。

示例代码 1:

package main

import (
	"context"
	"github.com/cloudwego/hertz/pkg/app"
	"github.com/cloudwego/hertz/pkg/app/middlewares/server/basic_auth"
	"github.com/cloudwego/hertz/pkg/app/server"
	"github.com/cloudwego/hertz/pkg/protocol/consts"
)

func main() {
	h := server.Default(server.WithHostPorts("127.0.0.1:8080"))
	// use middleware
	v1 := h.Group("/v1", basic_auth.BasicAuth(map[string]string{"test": "test"}))

	v1.GET("/ping", func(c context.Context, ctx *app.RequestContext) {
		ctx.String(consts.StatusOK,"ping")
	})
	h.Spin()
}

示例代码 2:

package main

import (
	"context"
	"github.com/cloudwego/hertz/pkg/app"
	"github.com/cloudwego/hertz/pkg/app/middlewares/server/basic_auth"
	"github.com/cloudwego/hertz/pkg/app/server"
	"github.com/cloudwego/hertz/pkg/protocol/consts"
)

func main() {
	h := server.Default(server.WithHostPorts("127.0.0.1:8080"))
	v1 := h.Group("/v1")
	// use `Use` method
	v1.Use(basic_auth.BasicAuth(map[string]string{"test": "test"}))
	v1.GET("/ping", func(c context.Context, ctx *app.RequestContext) {
		ctx.String(consts.StatusOK,"ping")
	})
	h.Spin()
}

路由类型

Hertz 支持丰富的路由类型用于实现复杂的功能,包括静态路由、参数路由 (命名参数、通配参数)。

路由的优先级:静态路由 > 命名参数路由 > 通配参数路由

刚刚所演示的都是静态路由,我们接下来看点别的。

命名参数路由

Hertz 支持使用 :name 这样的命名参数设置路由,并且命名参数只匹配单个路径段。

如果我们设置/user/:name路由,匹配情况如下

路径是否匹配
/user/gordon匹配
/user/you匹配
/user/gordon/profile不匹配
/user/不匹配

通过使用 RequestContext.Param 方法,我们可以获取路由中携带的参数。

示例代码:

package main

import (
	"context"
	"github.com/cloudwego/hertz/pkg/app"
	"github.com/cloudwego/hertz/pkg/app/server"
	"github.com/cloudwego/hertz/pkg/protocol/consts"
)

func main(){
	h := server.Default(server.WithHostPorts("127.0.0.1:8080"))
	// This handler will match: "/hertz/version", but will not match : "/hertz/" or "/hertz"
	h.GET("/hertz/:version", func(ctx context.Context, c *app.RequestContext) {
		version := c.Param("version")
		c.String(consts.StatusOK, "Hello %s", version)
	})
	h.Spin()
}

 

通配参数路由

Hertz 支持使用 *path 这样的通配参数设置路由,并且通配参数会匹配所有内容。

如果我们设置/src/*path路由,匹配情况如下

路径是否匹配
/src/匹配
/src/somefile.go匹配
/src/subdir/somefile.go匹配

通过使用 RequestContext.Param 方法,我们可以获取路由中携带的参数。

示例代码:

package main

import (
	"context"
	"github.com/cloudwego/hertz/pkg/app"
	"github.com/cloudwego/hertz/pkg/app/server"
	"github.com/cloudwego/hertz/pkg/protocol/consts"
)

func main(){
	h := server.Default(server.WithHostPorts("127.0.0.1:8080"))
	// However, this one will match "/hertz/v1/" and "/hertz/v2/send"
	h.GET("/hertz/:version/*action", func(ctx context.Context, c *app.RequestContext) {
		version := c.Param("version")
		action := c.Param("action")
		message := version + " is " + action
		c.String(consts.StatusOK, message)
	})
	h.Spin()
}

获取路由注册信息

Hertz 提供了 Routes 获取注册的路由信息供用户使用。

路由信息结构:

// RouteInfo represents a request route's specification which contains method and path and its handler.
type RouteInfo struct {
    Method      string   // http method
    Path        string   // url path
    Handler     string   // handler name
    HandlerFunc app.HandlerFunc
}

// RoutesInfo defines a RouteInfo array.
type RoutesInfo []RouteInfo

示例代码:

package main

import (
	"context"
	"github.com/cloudwego/hertz/pkg/app"
	"github.com/cloudwego/hertz/pkg/app/server"
	"github.com/cloudwego/hertz/pkg/common/hlog"
	"github.com/cloudwego/hertz/pkg/common/utils"
	"github.com/cloudwego/hertz/pkg/protocol/consts"
)

func main() {
	h := server.Default()
	h.GET("/ping", func(c context.Context, ctx *app.RequestContext) {
		ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"})
	})
	routeInfo := h.Routes()
	hlog.Info(routeInfo)
	h.Spin()
}

NoRoute 与 NoMethod 使用

Hertz 提供了 NoRoute 与 NoMethod 方法用于全局处理 HTTP 404 与 405 请求。 当使用 NoMethod 时需要与 WithHandleMethodNotAllowed 配合使用。

示例代码:

package main

import (
	"context"
	"github.com/cloudwego/hertz/pkg/app"
	"github.com/cloudwego/hertz/pkg/app/server"
	"github.com/cloudwego/hertz/pkg/common/utils"
	"github.com/cloudwego/hertz/pkg/protocol/consts"
)

func main() {
	h := server.Default(server.WithHandleMethodNotAllowed(true))
	h.POST("/ping", func(c context.Context, ctx *app.RequestContext) {
		ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"})
	})
	// set NoRoute handler
	h.NoRoute(func(c context.Context, ctx *app.RequestContext) {
		ctx.String(consts.StatusOK, "no route")
	})
	// set NoMethod handler
	h.NoMethod(func(c context.Context, ctx *app.RequestContext) {
		ctx.String(consts.StatusOK, "no method")
	})

	h.Spin()
}

重定向尾斜杠

Hertz 在默认情况下会根据请求 path 末尾的 / 自动进行转发。如果 router 中只有 /foo/,那么请求 /foo 会被自动重定向到 /foo/;如果 router 中只有 /foo,那么 /foo/ 会被重定向到 /foo。

这样的请求除 GET 以外的请求方法都会触发 307 Temporary Redirect 状态码,而 GET 请求会触发 301 Moved Permanently 状态码。

可以在配置中取消,如下:

package main

import "github.com/cloudwego/hertz/pkg/app/server"

func main() {
    h := server.New(server.WithRedirectTrailingSlash(false))
	...
}

3、客户端

package main

import (
	"context"
	"fmt"
	
	"github.com/cloudwego/hertz/pkg/app"
	"github.com/cloudwego/hertz/pkg/app/client"
	"github.com/cloudwego/hertz/pkg/app/server"
	"github.com/cloudwego/hertz/pkg/protocol"
	"github.com/cloudwego/hertz/pkg/protocol/consts"
)

func performRequest() {
	c, _ := client.NewClient()
	req, resp := protocol.AcquireRequest(), protocol.AcquireResponse()
	req.SetRequestURI("http://localhost:8080/hello")

	req.SetMethod("GET")
	_ = c.Do(context.Background(), req, resp)
	fmt.Printf("get response: %s\n", resp.Body())  // status == 200 resp.Body() == []byte("hello hertz")
}

func main() {
	h := server.New(server.WithHostPorts(":8080"))
	h.GET("/hello", func(c context.Context, ctx *app.RequestContext) {
		ctx.JSON(consts.StatusOK, "hello hertz")
	})
	go performRequest()
	h.Spin()
}

 

Client 配置

配置项默认值描述
WithDialTimeout1s拨号超时时间
WithMaxConnsPerHost512每个主机可能建立的最大连接数
WithMaxIdleConnDuration10s最大的空闲连接持续时间,空闲的连接在此持续时间后被关闭
WithMaxConnDuration0s最大的连接持续时间,keep-alive 连接在此持续时间后被关闭
WithMaxConnWaitTimeout0s等待空闲连接的最大时间
WithKeepAlivetrue是否使用 keep-alive 连接,默认使用
WithClientReadTimeout0s完整读取响应(包括 body)的最大持续时间
WithTLSConfignil设置用于创建 tls 连接的 tlsConfig,具体配置信息请看 tls
WithDialernetwork.Dialer设置指定的拨号器
WithResponseBodyStreamfalse是否在流中读取 body,默认不在流中读取
WithDisableHeaderNamesNormalizingfalse是否禁用头名称规范化,默认不禁用,如 cONTENT-lenGTH -> Content-Length
WithName""用户代理头中使用的客户端名称
WithNoDefaultUserAgentHeaderfalse是否没有默认的 User-Agent 头,默认有 User-Agent 头
WithDisablePathNormalizingfalse是否禁用路径规范化,默认规范路径,如 http://localhost:8080/hello/../ hello -> http://localhost:8080/hello
WithRetryConfignilHTTP 客户端的重试配置,重试配置详细说明请看 重试
WithWriteTimeout0sHTTP 客户端的写入超时时间
WithConnStateObservenil, 5s设置观察和记录 HTTP 客户端的连接状态的函数以及观察执行间隔
WithDialFuncnetwork.Dialer设置 HTTP 客户端拨号器函数,会覆盖自定义拨号器

示例代码:

func main() {
	observeInterval := 10 * time.Second
	stateFunc := func(state config.HostClientState) {
		fmt.Printf("state=%v\n", state.ConnPoolState().Addr)
	}
	var customDialFunc network.DialFunc = func(addr string) (network.Conn, error) {
		return nil, nil
	}
	c, err := client.NewClient(
		client.WithDialTimeout(1*time.Second),
		client.WithMaxConnsPerHost(1024),
		client.WithMaxIdleConnDuration(10*time.Second),
		client.WithMaxConnDuration(10*time.Second),
		client.WithMaxConnWaitTimeout(10*time.Second),
		client.WithKeepAlive(true),
		client.WithClientReadTimeout(10*time.Second),
		client.WithDialer(standard.NewDialer()),
		client.WithResponseBodyStream(true),
		client.WithDisableHeaderNamesNormalizing(true),
		client.WithName("my-client"),
		client.WithNoDefaultUserAgentHeader(true),
		client.WithDisablePathNormalizing(true),
		client.WithRetryConfig(
			retry.WithMaxAttemptTimes(3),
			retry.WithInitDelay(1000),
			retry.WithMaxDelay(10000),
			retry.WithDelayPolicy(retry.DefaultDelayPolicy),
			retry.WithMaxJitter(1000),
		),
		client.WithWriteTimeout(10*time.Second),
		client.WithConnStateObserve(stateFunc, observeInterval),
		client.WithDialFunc(customDialFunc, netpoll.NewDialer()),
	)
	if err != nil {
		return
	}

	status, body, _ := c.Get(context.Background(), nil, "http://www.example.com")
	fmt.Printf("status=%v body=%v\n", status, string(body))
}

 

Client Request 配置

配置项默认值描述
WithDialTimeout0s拨号超时时间,该配置项的优先级高于 Client 配置,即会覆盖相应的 Client 配置项
WithReadTimeout0s完整读取响应(包括 body)的最大持续时间,该配置项的优先级高于 Client 配置,即会覆盖相应的 Client 配置项
WithWriteTimeout0sHTTP 客户端的写入超时时间,该配置项的优先级高于 Client 配置,即会覆盖相应的 Client 配置项
WithRequestTimeout0s完整的 HTTP 请求的超时时间
WithTagmake(map[string]string)以 key-value 形式设置 tags 字段,配合服务发现使用,详情见 WithTag
WithSDfalse配合服务发现使用,传递 true 时,本次请求使用服务发现,详情见 WithSD

示例代码:

func main() {
	cli, err := client.NewClient()
	if err != nil {
		return
	}
	req, res := &protocol.Request{}, &protocol.Response{}
	req.SetOptions(config.WithDialTimeout(1*time.Second),
		config.WithReadTimeout(3*time.Second),
		config.WithWriteTimeout(3*time.Second),
		config.WithReadTimeout(5*time.Second),
		config.WithSD(true),
		config.WithTag("tag", "tag"))
	req.SetMethod(consts.MethodGet)
	req.SetRequestURI("http://www.example.com")
	err = cli.Do(context.Background(), req, res)
	fmt.Printf("resp = %v,err = %+v", string(res.Body()), err)
}

发送请求

func (c *Client) Do(ctx context.Context, req *protocol.Request, resp *protocol.Response) error
func (c *Client) DoRedirects(ctx context.Context, req *protocol.Request, resp *protocol.Response, maxRedirectsCount int) error
func (c *Client) Get(ctx context.Context, dst []byte, url string, requestOptions ...config.RequestOption) (statusCode int, body []byte, err error)
func (c *Client) Post(ctx context.Context, dst []byte, url string, postArgs *protocol.Args, requestOptions ...config.RequestOption) (statusCode int, body []byte, err error)

Do

Do 函数执行给定的 http 请求并填充给定的 http 响应。请求必须包含至少一个非零的 RequestURI,其中包含完整的 URL 或非零的 Host header + RequestURI。

该函数不会跟随重定向,请使用 Get 函数或 DoRedirects 函数或 Post 函数来跟随重定向。

如果 resp 为 nil,则会忽略响应。如果所有针对请求主机的 DefaultMaxConnsPerHost 连接都已忙,则会返回 ErrNoFreeConns 错误。在性能关键的代码中,建议通过 AcquireRequest 和 AcquireResponse 获取 req 和 resp。

函数签名:

func (c *Client) Do(ctx context.Context, req *protocol.Request, resp *protocol.Response) error 

示例代码:

func main() {
	// hertz server:http://localhost:8080/ping ctx.String(consts.StatusOK, "pong")
	c, err := client.NewClient()
	if err != nil {
		return
	}

	req, res := &protocol.Request{}, &protocol.Response{}
	req.SetMethod(consts.MethodGet)
	req.SetRequestURI("http://localhost:8080/ping")

	err = c.Do(context.Background(), req, res)
	fmt.Printf("resp = %v,err = %+v", string(res.Body()), err)
	// resp.Body() == []byte("pong") err == <nil>
}

DoRedirects

DoRedirects 函数执行给定的 http 请求并填充给定的 http 响应,遵循最多 maxRedirectsCount 次重定向。当重定向次数超过 maxRedirectsCount 时,将返回 ErrTooManyRedirects 错误。

函数签名:

func (c *Client) DoRedirects(ctx context.Context, req *protocol.Request, resp *protocol.Response, maxRedirectsCount int) error

示例代码:

func main() {
	// hertz server
	// http://localhost:8080/redirect ctx.Redirect(consts.StatusMovedPermanently, []byte("/redirect2"))
	// http://localhost:8080/redirect2 ctx.Redirect(consts.StatusMovedPermanently, []byte("/redirect3"))
	// http://localhost:8080/redirect3 ctx.String(consts.StatusOK, "pong")

	c, err := client.NewClient()
	if err != nil {
		return
	}

	req, res := &protocol.Request{}, &protocol.Response{}
	req.SetMethod(consts.MethodGet)
	req.SetRequestURI("http://localhost:8080/redirect")

	err = c.DoRedirects(context.Background(), req, res, 1)
	fmt.Printf("resp = %v,err = %+v\n", string(res.Body()), err)
	// res.Body() == []byte("") err.Error() == "too many redirects detected when doing the request"

	err = c.DoRedirects(context.Background(), req, res, 2)
	fmt.Printf("resp = %v,err = %+v\n", string(res.Body()), err)
	// res.Body() == []byte("pong") err == <nil>
}

Get

Get 函数返回 URL 的状态码和响应体。如果 dst 太小,则将被响应体替换并返回,否则将分配一个新的切片。

该函数会自动跟随重定向。

函数签名:

func (c *Client) Get(ctx context.Context, dst []byte, url string, requestOptions ...config.RequestOption) (statusCode int, body []byte, err error)

示例代码:

func main() {
	// hertz server:http://localhost:8080/ping ctx.String(consts.StatusOK, "pong")
	c, err := client.NewClient()
	if err != nil {
		return
	}
	status, body, err := c.Get(context.Background(), nil, "http://localhost:8080/ping")
	fmt.Printf("status=%v body=%v err=%v\n", status, string(body), err)
	// status == 200 res.Body() == []byte("pong") err == <nil>
}

Post

Post 函数使用给定的 POST 参数向指定的 URL 发送 POST 请求。如果 dst 太小,则将被响应体替换并返回,否则将分配一个新的切片。

该函数会自动跟随重定向。

如果 postArgs 为 nil,则发送空的 POST 请求体。

函数签名:

func (c *Client) Post(ctx context.Context, dst []byte, url string, postArgs *protocol.Args, requestOptions ...config.RequestOption) (statusCode int, body []byte, err error)

示例代码:

func main() {
	// hertz server:http://localhost:8080/hello ctx.String(consts.StatusOK, "hello %s", ctx.PostForm("name"))
	c, err := client.NewClient()
	if err != nil {
		return
	}

	var postArgs protocol.Args
	postArgs.Set("name", "cloudwego") // Set post args
	status, body, err := c.Post(context.Background(), nil, "http://localhost:8080/hello", &postArgs)
	fmt.Printf("status=%v body=%v err=%v\n", status, string(body), err)
	// status == 200 res.Body() == []byte("hello cloudwego") err == <nil>
}

请求超时

注意:Do、DoRedirects、Get、Post 等请求函数可以通过 WithRequestTimeout 设置请求超时时间,DoTimeout 和 DoDeadline 函数通过传参的形式设置请求超时时间,两者都是修改 RequestOptions.requestTimeout 字段,所以在使用 DoTimeout 和 DoDeadline 函数时无需使用 WithRequestTimeout 函数,若同时使用了,请求超时时间以最后一次设置的为准。

func WithRequestTimeout(t time.Duration) RequestOption
func (c *Client) DoTimeout(ctx context.Context, req *protocol.Request, resp *protocol.Response, timeout time.Duration) error
func (c *Client) DoDeadline(ctx context.Context, req *protocol.Request, resp *protocol.Response, deadline time.Time) error

WithRequestTimeout

Do、DoRedirects、Get、Post 等请求函数虽然不能以传参的方式设置请求超时返回,但可以通过 Client Request 配置 中的 WithRequestTimeout 配置项来设置请求超时返回。

示例代码:

func main() {
	c, err := client.NewClient()
	if err != nil {
		return
	}

	// Do
	req, res := &protocol.Request{}, &protocol.Response{}
	req.SetOptions(config.WithRequestTimeout(5 * time.Second))
	req.SetMethod(consts.MethodGet)
	req.SetRequestURI("http://localhost:8888/get")
	err = c.Do(context.Background(), req, res)

	// DoRedirects
	err = c.DoRedirects(context.Background(), req, res, 5)

	// Get
	_, _, err = c.Get(context.Background(), nil, "http://localhost:8888/get", config.WithRequestTimeout(5*time.Second))

	// Post
	postArgs := &protocol.Args{}
	_, _, err = c.Post(context.Background(), nil, "http://localhost:8888/post", postArgs, config.WithRequestTimeout(5*time.Second))
}

DoTimeout

DoTimeout 函数执行给定的请求并在给定的超时时间内等待响应。

该函数不会跟随重定向,请使用 Get 函数或 DoRedirects 函数或 Post 函数来跟随重定向。

如果 resp 为 nil,则会忽略响应。如果在给定的超时时间内未能收到响应,则会返回 errTimeout 错误。

函数签名:

func (c *Client) DoTimeout(ctx context.Context, req *protocol.Request, resp *protocol.Response, timeout time.Duration) error

示例代码:

func main() {
	// hertz server:http://localhost:8080/ping ctx.String(consts.StatusOK, "pong") biz handler time: 1.5s
	c, err := client.NewClient()
	if err != nil {
		return
	}

	req, res := &protocol.Request{}, &protocol.Response{}
	req.SetMethod(consts.MethodGet)
	req.SetRequestURI("http://localhost:8080/ping")

	err = c.DoTimeout(context.Background(), req, res, time.Second*3)
	fmt.Printf("resp = %v,err = %+v\n", string(res.Body()), err)
	// res.Body() == []byte("pong") err == <nil>

	err = c.DoTimeout(context.Background(), req, res, time.Second)
	fmt.Printf("resp = %v,err = %+v\n", string(res.Body()), err)
	// res.Body() == []byte("") err.Error() == "timeout"
}

DoDeadline

DoDeadline 执行给定的请求并等待响应,直至给定的最后期限。

该函数不会跟随重定向,请使用 Get 函数或 DoRedirects 函数或 Post 函数来跟随重定向。

如果 resp 为 nil,则会忽略响应。如果在给定的截止日期之前未能收到响应,则会返回 errTimeout 错误。

函数签名:

func (c *Client) DoDeadline(ctx context.Context, req *protocol.Request, resp *protocol.Response, deadline time.Time) error

示例代码:

func main() {
	// hertz server:http://localhost:8080/ping ctx.String(consts.StatusOK, "pong") biz handler time: 1.5s
	c, err := client.NewClient()
	if err != nil {
		return
	}

	req, res := &protocol.Request{}, &protocol.Response{}
	req.SetMethod(consts.MethodGet)
	req.SetRequestURI("http://localhost:8080/ping")

	err = c.DoDeadline(context.Background(), req, res, time.Now().Add(3*time.Second))
	fmt.Printf("resp = %v,err = %+v\n", string(res.Body()), err)
	// res.Body() == []byte("pong") err == <nil>

	err = c.DoDeadline(context.Background(), req, res, time.Now().Add(1*time.Second))
	fmt.Printf("resp = %v,err = %+v\n", string(res.Body()), err)
	// res.Body() == []byte("") err.Error() == "timeout"
}

请求重试

func (c *Client) SetRetryIfFunc(retryIf client.RetryIfFunc)

SetRetryIfFunc

SetRetryIfFunc 方法用于自定义配置重试发生的条件。(更多内容请参考 retry-条件配置

函数签名:

func (c *Client) SetRetryIfFunc(retryIf client.RetryIfFunc)

示例代码:

func main() {
	c, err := client.NewClient()
	if err != nil {
		return
	}
	var customRetryIfFunc = func(req *protocol.Request, resp *protocol.Response, err error) bool {
		return true
	}
	c.SetRetryIfFunc(customRetryIfFunc)
	status2, body2, _ := c.Get(context.Background(), nil, "http://www.example.com")
	fmt.Printf("status=%v body=%v\n", status2, string(body2))
}

添加请求内容

Hertz 客户端可以在 HTTP 请求中添加 query 参数、www-url-encodedmultipart/form-datajson 等多种形式的请求内容。

示例代码:

func main() {
	client, err := client.NewClient()
	if err != nil {
		return
	}
	req := &protocol.Request{}
	res := &protocol.Response{}

	// Use SetQueryString to set query parameters
	req.Reset()
	req.Header.SetMethod(consts.MethodPost)
	req.SetRequestURI("http://127.0.0.1:8080/v1/bind")
	req.SetQueryString("query=query&q=q1&q=q2&vd=1")
	err = client.Do(context.Background(), req, res)
	if err != nil {
		return
	}

	// Send "www-url-encoded" request
	req.Reset()
	req.Header.SetMethod(consts.MethodPost)
	req.SetRequestURI("http://127.0.0.1:8080/v1/bind?query=query&q=q1&q=q2&vd=1")
	req.SetFormData(map[string]string{
		"form": "test form",
	})
	err = client.Do(context.Background(), req, res)
	if err != nil {
		return
	}

	// Send "multipart/form-data" request
	req.Reset()
	req.Header.SetMethod(consts.MethodPost)
	req.SetRequestURI("http://127.0.0.1:8080/v1/bind?query=query&q=q1&q=q2&vd=1")
	req.SetMultipartFormData(map[string]string{
		"form": "test form",
	})
	err = client.Do(context.Background(), req, res)
	if err != nil {
		return
	}

	// Send "Json" request
	req.Reset()
	req.Header.SetMethod(consts.MethodPost)
	req.Header.SetContentTypeBytes([]byte("application/json"))
	req.SetRequestURI("http://127.0.0.1:8080/v1/bind?query=query&q=q1&q=q2&vd=1")
	data := struct {
		Json string `json:"json"`
	}{
		"test json",
	}
	jsonByte, _ := json.Marshal(data)
	req.SetBody(jsonByte)
	err = client.Do(context.Background(), req, res)
	if err != nil {
		return
	}
}

上传文件

Hertz 客户端支持向服务器上传文件。

示例代码:

func main() {
	client, err := client.NewClient()
	if err != nil {
		return
	}
	req := &protocol.Request{}
	res := &protocol.Response{}
	req.SetMethod(consts.MethodPost)
	req.SetRequestURI("http://127.0.0.1:8080/singleFile")
	req.SetFile("file", "your file path")

	err = client.Do(context.Background(), req, res)
	if err != nil {
		return
	}
	fmt.Println(err, string(res.Body()))
}

流式读响应内容 

 

服务发现

Hertz 客户端支持通过服务发现寻找目标服务器。

Hertz 支持自定义服务发现模块,更多内容可参考 服务发现拓展

Hertz 目前已接入的服务发现中心相关内容可参考 服务注册与发现

TLS

Hertz 客户端默认使用的网络库 netpoll 不支持 TLS,如果要配置 TLS 访问 https 地址,应该使用标准库。

TLS 相关的配置信息可参考 tls

正向代理

func (c *Client) SetProxy(p protocol.Proxy)

SetProxy

SetProxy 用来设置客户端代理。(更多内容请参考 正向代理

注意:同一个客户端不能设置多个代理,如果需要使用另一个代理,请创建另一个客户端并为其设置代理。

示例代码:

func (c *Client) SetProxy(p protocol.Proxy)

函数签名:

func main() {
	// Proxy address
	proxyURL := "http://<__user_name__>:<__password__>@<__proxy_addr__>:<__proxy_port__>"

	parsedProxyURL := protocol.ParseURI(proxyURL)
	client, err := client.NewClient(client.WithDialer(standard.NewDialer()))
	if err != nil {
		return
	}
	client.SetProxy(protocol.ProxyURI(parsedProxyURL))
	upstreamURL := "http://google.com"
	_, body, _ := client.Get(context.Background(), nil, upstreamURL)
	fmt.Println(string(body))
}

关闭空闲连接

func (c *Client) CloseIdleConnections()

CloseIdleConnections

CloseIdleConnections 方法用于关闭任何处于空闲状态的 keep-alive 连接。这些连接可能是之前的请求所建立的,但现在已经空闲了一段时间。该方法不会中断任何当前正在使用的连接。

函数签名:

func (c *Client) CloseIdleConnections()

示例代码:

func main() {
    c, err := client.NewClient()
    if err != nil {
        return
    }
    status, body, _ := c.Get(context.Background(), nil, "http://www.example.com")
    fmt.Printf("status=%v body=%v\n", status, string(body))

    // close idle connections
    c.CloseIdleConnections()
}

中间件

func (c *Client) Use(mws ...Middleware)
func (c *Client) UseAsLast(mw Middleware) error
func (c *Client) TakeOutLastMiddleware() Middleware

Use

使用 Use 方法对当前 client 增加一个中间件。(更多内容请参考 客户端中间件

函数签名:

func (c *Client) Use(mws ...Middleware)

UseAsLast

UseAsLast 函数将中间件添加到客户端中间件链的最后。

如果客户端中间件链在之前已经设置了最后一个中间件,UseAsLast 函数将会返回 errorLastMiddlewareExist 错误。因此,为确保客户端中间件链的最后一个中间件为空,可以先使用 TakeOutLastMiddleware 函数清空客户端中间件链的最后一个中间件。

注意:UseAsLast 函数将中间件设置在了 c.lastMiddleware 中,而使用 Use 函数设置的中间件链存放在c.mws中,两者相对独立,只是在执行客户端中间件链的最后才执行 c.lastMiddleware,因此 UseAsLast 函数在 Use 函数之前或之后调用皆可。

函数签名:

func (c *Client) UseAsLast(mw Middleware) error

示例代码:

func main() {
	client, err := client.NewClient()
	if err != nil {
		return
	}
	client.Use(MyMiddleware)
	client.UseAsLast(LastMiddleware)
	req := &protocol.Request{}
	res := &protocol.Response{}
	req.SetRequestURI("http://www.example.com")
	err = client.Do(context.Background(), req, res)
	if err != nil {
		return
	}
}

4、网络库

Hertz 默认集成了 Netpoll 和 Golang 原生网络库两个网络库,用户可以根据自己的场景选择合适的网络库以达到最佳性能。

使用方式

对于 Server 来说,默认使用 netpoll,可以通过配置项进行更改:

注意:netpoll 目前不支持 Windows,Windows 会通过条件编译将网络库自动切换为 go net。

server.New(server.WithTransport(standard.NewTransporter))
server.New(server.WithTransport(netpoll.NewTransporter))

对于 Client 来说,可以通过配置项进行更改:

client.NewClient(client.WithDialer(standard.NewDialer()))
client.NewClient(client.WithDialer(netpoll.NewDialer()))

网络库选择

  1. 如果有启动 TLS Server 的需求,请使用 go net 网络库。netpoll 正在实现对 TLS 的支持。
  2. 由于网络库触发模式的不同:go net 为 ET 模型,netpoll 为 LT 模型,使得两个网络库的适用场景有一些不同。 在 ET 模型下,由框架处理 Read / Write 事件;在 LT 模型下,由网络库处理 Read / Write 事件。 使得在小包场景下,由于更优的调度策略使得 LT 性能更好;在大包场景下,由于读 / 写不受框架层控制,使得大量数据被读入内存而不能及时处理,可能会造成内存压力。
  • 在较大 request size 下(request size > 1M),推荐使用 go net 网络库加流式。
  • 在其他场景下,推荐使用 netpoll 网络库,会获得极致的性能。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值